GithubHelp home page GithubHelp logo

freedomlayer / offset Goto Github PK

View Code? Open in Web Editor NEW
163.0 163.0 20.0 7.64 MB

Offset payment engine

Home Page: https://www.offsetcredit.org

License: Other

Rust 98.03% Cap'n Proto 1.57% Shell 0.40%
credit-card decentralized federated micropayments payments protocol rust

offset's People

Contributors

a4vision avatar amosonn avatar atul9 avatar cy6erlion avatar gamwe6 avatar juchiast avatar kamyuentse avatar pzmarzly avatar realcr avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

offset's Issues

Separating IndexServer logic against client into two ports

Currently the messages between IndexServer and IndexClient are as follows:

pub enum IndexServerToClient {
    TimeHash(HashResult),
    ResponseRoutes(ResponseRoutes),
}

pub enum IndexClientToServer {
    MutationsUpdate(MutationsUpdate),
    RequestRoutes(RequestRoutes),
}

This interface is used for two different functions:

  1. Requesting routes: (RequestRoutes, ResponseRoutes).
  2. Updating friends state (TimeHash, MutationsUpdate).

Those two parts are unrelated. It might be possible that some user of the server may only want to use one function out of the two. For example: a user might only want to request routes (RequestRoutes/ResponseRoutes). In this case the user still has to deal with incoming TimeHash messages.

Cons of this design change are that index servers will now have two ports instead of one port facing clients. (Note that there is a third port facing other index servers).

Design the module level Error for crypto

I am trying to fix the problem in crypto, I notice that some unwrap usage here, and we should use a reasonable Error instead of unwrap, and try to make this module unwrap free.

BTW, I plan to use the define_ty(Yep, I don't have a better name for it, define_fixed_bytes?) to replace the boring job here. Including SymmetricKey, Salt, Signature, DhPublicKey, RandValue, am I miss something?

Use "send the sender" trick for all Database interfaces

Change all the request/response style interfaces with the Database module to use "send the sender" trick. In other words: Add to all request messages a field response_sender: mpsc::Sender. The Database module will then use the obtained sender to send the response.

In addition, we need to add the possibility of failure when trying to obtain something from the database. ResponseLoadFriendToken and ResponseLoadNeighborToken for example, should be able to indicate that no such Friend or no such Neighbor exist.

@kamyuentse: It is possible that some code at the Database module needs to be changed to allow these modifications. I will check this.

Having rust nightly version pinned in a single place

We currently have the pinned rust nightly version in use scattered in a few places.
(1) The file: rust-toolchain (See here: https://github.com/rust-lang/rustup.rs#the-toolchain-file)
(2) .travis.yml contains the used nightly version in three places (See snippet below).

This means that every time we bump rust nightly's version we have to modify all those places at the same time. Is it somehow possible for us to make .travis.yml find the pinned rust nightly version from the rust-toolchain file automatically?

Snippet from .travis.yml (Containing rust nightly version 3 times):

  - env: TARGET=x86_64-unknown-linux-gnu CC=gcc-6 CXX=g++-6 RUSTFLAGS="-C link-dead-code"
    os: linux
    dist: trusty
    rust: nightly-2019-06-07
    addons:
      apt:
        packages:
        - gcc-6
        - g++-6
        sources:
        - ubuntu-toolchain-r-test
    script:
    - travis/trusty/pre/cargo-config.sh
    - travis/trusty/pre/capnp.sh
    - cargo fmt --all -- --check
      # TODO: Enable these two lints when clippy bugs are solved:
    - cargo clippy -- -A clippy::needless_lifetimes -A clippy::identity_conversion
      # We add target dir so that kcov can find the test files to run:
    - cargo test --target ${TARGET}
    - travis/trusty/post/kcov/try-install.sh
    - travis/trusty/post/kcov/run.sh

  - env: TARGET=x86_64-apple-darwin
    os: osx
    rust: nightly-2019-06-07
    osx_image: xcode9.4.1
    before_script:
    - brew install capnp
    script:
    - cargo test

  - env: TARGET=x86_64-pc-windows-msvc
    os: windows
    # Used as a temporary fix for windows CI issue in travis.
    # See: https://travis-ci.community/t/windows-instances-hanging-before-install/250/25
    filter_secrets: false
    rust: nightly-2019-06-07
    ...

EDIT: See: travis-ci/travis-build#1513

CI problems

There seems to be some problem with the CI process.
I think that things leak between different branches. Some observations:

  • It seems like the badge for master reports an error that actually originates from PR #205, which was not yet merged.
  • I think that I noticed test coverage results from some PR leak to the master badge before I merged that PR into master.

@pzmarzly : Do you have an idea what could the problem?
If you believe this is cache related (As mentioned earlier), I'm cool with removing the cache, at least for a while. I rather have slow CI over a wrong CI.

EDIT:

One example: When I currently enter: https://travis-ci.com/freedomlayer/offst (By clicking on the CI badge on master), I see the error from PR 205 written for branch master.

Automatic conversion from capnp to Rust

We currently manually convert between Capnp structures and Rust structures in offst-proto.
This part of the code is very long, repetitive and error prone. Example from app_server/serialize.rs:

fn ser_reset_friend_channel(
    reset_friend_channel: &ResetFriendChannel,
    reset_friend_channel_builder: &mut app_server_capnp::reset_friend_channel::Builder,
) {
    write_public_key(
        &reset_friend_channel.friend_public_key,
        &mut reset_friend_channel_builder
            .reborrow()
            .init_friend_public_key(),
    );

    write_signature(
        &reset_friend_channel.reset_token,
        &mut reset_friend_channel_builder.reborrow().init_reset_token(),
    );
}

fn deser_reset_friend_channel(
    reset_friend_channel_reader: &app_server_capnp::reset_friend_channel::Reader,
) -> Result<ResetFriendChannel, SerializeError> {
    Ok(ResetFriendChannel {
        friend_public_key: read_public_key(&reset_friend_channel_reader.get_friend_public_key()?)?,
        reset_token: read_signature(&reset_friend_channel_reader.get_reset_token()?)?,
    })
}

This is something that can probably be solved using Rust procedural macros. I wasn't sure this is a viable solution until I have seen Libra's solution:
https://github.com/libra/libra/tree/master/common/proto_conv

Libra's relies on protobuf for serialization, but it seems to me like the macro conversion concept is similar.

The general plan is to have something like:

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[CapnpType(crate::funder::messages::AppPermissions)]
pub struct AppPermissions {
    /// Can request routes
    pub routes: bool,
    /// Can send credits as a buyer
    pub buyer: bool,
    /// Can receive credits as a seller
    pub seller: bool,
    /// Can configure friends
    pub config: bool,
}

Which automatically gives rust_object.into_capnp(), and RustType::from_capnp(...).

Some things to consider:

  1. We need to make sure our Rust structures have exactly the same field names as their capnp counterparts. I think that this is mostly true in Offst, but there might be some mistakes that needs to be fixed.
  2. I am not sure if we need to create our own primitive types. It seems like in Libra's code base new primitive types were created.
  3. The two things that I think might be tricky to implement are Capnp's sum types (unions) and List. I'm not yet sure how to implement those.

@pzmarzly : Is this something you would like to try out? I don't know much about proc macros at this point. If you have some time maybe you can explore a possible solution here.

EDIT: I will try to investigate what can be done here, hopefully I can come up with a good idea for implementation. I have to learn some stuff first.

Upgrade to Futures 0.2

In the future we might have to upgrade to upgrade CSwitch to use the newer Tokio Futures design. Currently CSwitch works with Futures 0.1.

I am still not sure if we want to do the upgrade only when Futures becomes stable (I heard that 1.0 might happen during 2018), or should already start doing it at Futures 0.2.

I was wondering what is the right way to do this kind of upgrade.

  • Can we upgrade the code gradually, or do we have to do it all at once?
  • Which crates should be put at Cargo.toml for the new futures? Should we use the separated smaller crates of future?

Extra chosen relay resiliency

In the current design every node can pick one relay. The node then opens a "listen" connection to the relay and waits for connections from other nodes. In a sense, in the current design the relay is like a mail server that a user chooses to receive messages through.

The current design has two major issues:

  • lock-in to a specific relay.
  • lack of fault tolerance for failing relays.

lock-in: If a node wants to change his chosen relay, he has to notify all of his friends to make sure they can connect to him in the future. This creates a lock-in to a specific relay, making it difficult for a node to change relays.

lack of fault tolerance: Currently a node depends on a single relay to receive connections from remote nodes. If this relay is offline, the node will not be able to communicate with about half of his friends. (Half of the friends initiate connection, and half of the friends wait for connection. Therefore it will not be possible to communicate with the half the initiate connection).

A possible solution to these problem is as follows:

  • Every node will listen to multiple relays.

  • Whenever a node wants to change his relays list, the node will notify all his friends about the change through the token channel. The change will fully take place only one token channel message later, to make sure the remote side has received the change message.

  • Every node will maintain his wanted relays list, but will also maintain the relay lists as they were last sent to his friends.

For example, consider a node A with friends B and C:

A --- B
|
C
  • A listens on relays R1, R2, R3.
  • B knows that A could be found on one of the relays R1, R2, R3.
  • C knows that A could be found on one of the relays R1, R2, R3.

Whenever B wants to connect to A, B will first attempt to connect to R1. If R1 is down, or A is not found on R1, B will try R2. If connection through R2 fails, B will try R3.

Suppose that A wants to change his relays list to {R4, R5}. In this case A will notify B and C about the change through a token channel message. Suppose that B was online and was notified about the change, but C was not online. In this case A will now have to listen on R1, R2, R3, R4 and R5.

At some point C will connect to A. A will then notify C that the new relays list is {R4, R5}. A will disconnect from R1,R2,R3 only when a token channel message is received from C, proving that C has received the notification message about changing relays list.

Unnecessary Box::pin()

Some places in the code base use Box::pin(...) only to create a future that satisfies Pin requirements. One example from components/channeler/src/connect_pool.rs:

async fn conn_attempt<RA, C, ET>(
    friend_public_key: PublicKey,
    address: RA,
    mut client_connector: C,
    mut encrypt_transform: ET,
    canceler: oneshot::Receiver<()>,
) -> Option<RawConn>
where
    RA: Eq,
    C: FutTransform<Input = (RA, PublicKey), Output = Option<RawConn>> + Clone,
    ET: FutTransform<Input = (PublicKey, RawConn), Output = Option<RawConn>> + Clone,
{
    // TODO: How to remove this Box::pin?
    let connect_fut = Box::pin(async move {
        let raw_conn = await!(client_connector.transform((address, friend_public_key.clone())))?;
        await!(encrypt_transform.transform((friend_public_key.clone(), raw_conn)))
    });

    // We either finish connecting, or got canceled in the middle:
    select! {
        connect_fut = connect_fut.fuse() => connect_fut,
        _ = canceler.fuse() => None,
    }
}

Box::pin(...) is a runtime tradeoff we make only to satisfy the compiler. Is there a way to write this code without using Box::pin(...), and also without using unsafe tricks? It seems to me that we should be able to pin the future to the stack somehow, but I'm not sure if we are supposed to do this.

This is not high priority, but I was curious about this problem for a while. There are other places in the code that have the same problem, you can probably find them by searching for "Box::pin". (Note that there are some legitimate uses of Box::pin around the code too).

@pzmarzly : Do you think you can check this out?

Idea: using quickcheck for strong tests at the token channel code

Quickcheck is a rust crate: https://github.com/BurntSushi/quickcheck
It allows to test various predicates by automatically generating input, possibly in a smart way.
I am not fully sure how it works internally, but it looks very promising.

We might be able to use quickcheck to test some critical parts like the token channel state machine for the Networker and the Funder. I have no idea if this will increase the amount of time the tests run, but we might be able to separate the quickcheck tests from the rest of the tests.

This is an idea for the future, I wanted to make sure that I don't forget.

RFC 001: UDP Style Encryption

Introduction

Many encryption schemes rely on an underlying communication layer that guarantees the order of sent messages. This underlying communication layer is usually TCP. Examples of such encryption schemes are SSH and TLS. Such encryption schemes fail to work over a communication layer that does not guarantee ordered arrival of messages.

Consider SSH for example: In order to avoid replay attacks, SSH uses an incrementing counter for each side of the communication. Every message contains an internal counter that increments for every message. The receiving side always checks the internal counter of incoming messages. If the counter value does not match the expected value, the message is discarded.

Consider a UDP like communication layer between two parties A and B, where there is no guarantee for the ordered arrival of messages: Messages may be dropped or arrive out of order. We want to set up an encrypted channel over such a communication layer.

In this case, we can not use the countermeasure used by SSH to defend against replay attacks. We can not use an incrementing counter because we can not be sure that messages arrive at the remote party at the same order they were sent.

This document presents a design for an anti-replay mechanism to be used when encrypting UDP style communication layers. The idea is due to Zur, and thus called Zur's sliding window.

Problem setup

Problem setup

Consider two parties A and B sending datagrams over a nonreliable UDP style communication layer. A and B both have public keys, each party knows the public key of the other party.

An attacker E can see all the communication on the wire between A and B, and can send messages on the wire. E also knows the public keys of A and B.

A -----+------- B
       |
       |
       E 

Assume that A and B somehow negotiated a symmetric key for symmetric encryption that allows authentication (If A encrypts a message, B can know for sure that the encrypted message was created by A).

If A naively sends encrypted datagrams to B, E will be able to resend those datagrams to B, making B think that A has sent those messages again. We call this attack replay attack.

Why is this a security problem? Consider this example: A sends a message to open a gate, a person then enters a building, and finally A sends a message to close the gate. If E could replay the "open gate" message, E could enter the building.

A and B need to find a way to send encrypted messages so that E will not be able to replay those messages.

Connection setup

Connection setup

We consider two sides: client (A) and server (B). The client is the side that has the ability to initiate a connection. In the case of IP networks, for example, the client knows the IP address of the server, but the server does not necessarily know the IP address of the client.

In other cases, it is possible that both sides can play the role of the client (If for example, both sides have static known addresses).

Setting up an encrypted connection is done through a 4-way handshake, as follows:

  1. A -> B: InitChannel (randNonceA, publicKeyA)
  2. B -> A: ExchangePassive (hashPrev, randNonceB, publicKeyB, dhPublicKeyB, keySaltB, Signature(B))
  3. A -> B: ExchangeActive (hashPrev, dhPublicKeyA, keySaltA, Signature(A))
  4. B -> A: ChannelReady (hashPrev, Signature(B))

Where:

  • hashPrev is sha512/256 over the previous message [32 bytes]. This makes sure
    that the messages are ordered correctly.
  • signature(X) [64 bytes] means a signature over "{MessageName}" and all the
    previous fields in the message by party X.
  • publicKeyX and dhPublicKeyX are of size [32 bytes].
  • keySalt is of size [32 bytes].

B considers the connection to be up once message ChannelReady is sent. A considers the connection to be up once message ChannelReady is received.

After the connection was set up, each party has two ends of channels, one sending end and one receiving end. The channel from A to B is called channelAId := sha512/256("Init" || dhPublicKeyA || dhPublicKeyB || randNonceA || randNonceB)[:16].
The Channel from B to A is called: channelBId := sha512/256("Accp" || dhPublicKeyB || dhPublicKeyA || randNonceB || randNonceA)[:16].

Note: channelXId is of size [16 bytes].

Both parties A and B create two symmetric keys for sending and receiving messages (keySaltA is used for sending messages from A to B, keySaltB is used or sending messages from B to A). We denote the symmetric keys by symmetricKeyAB (Sending from A to B) and symmetricKeyBA (Sending from B to A).

Pay special attention to the distinction between the concept of a connection setup and a channel. A connection setup between A and B creates two new distinct channels: A channel for sending messages from A to B, and a channel for sending messages from B to A. Each channel makes use of a different symmetric encryption key.

TODO: Dealing with message drops during connection setup? How to maintain state for connection setup?

Channel receiving ends Carousel

Channel receiving ends Carousel

Each party X maintains a maximum of 3 receiving ends of channels from a given remote party Y. This is called the channels carousel mechanism. Whenever a new receiving end is created from Y (For example, if X or Y initiated a new connection using the four-way handshake), X will erase the oldest receiving channel it has from Y, and replace it with the new receiving end.

The channels carousel ensures that if there are still some messages from Y sent through an old channel, floating in the network, when those messages arrive to X, X will be able to receive them. If X maintained only one
receiving end of a channel from Y to X, those messages would have been lost.

Why does the channel carousel have 3 receiving ends of channels and not just 2?
It is possible that both parties X and Y attempt to setup a connection at the same time. In this case, two new receiving ends of channels will be created for each party. If the capacity of the channel carousel was only 2, the simultaneous setup of two connections would have erased all of the old receiving ends, and messages that were previously sent might be lost.

Note that while maximum of 3 receiving ends of channels are maintained against every remote party, only one sending end of a channel is required against every remote party.

We still haven't explained what a channel end (receiving end or sending end) contains, and how we plan to avoid replay attacks using our mechanism. This is explained in the next sections.

Memory representation for channel ends

Memory representation for channel ends

Upon connection setup, every party sets up the following state in memory for
the sending end of a channel:

SendingEndState

  • channelId [16 bytes]
  • sendCounter [12 bytes]
  • symmetricKey [32 bytes]

The initial value of sendCounter is 1.

To send a message M to through a channel to the remote party, the party will
send:

Enc{
    Key=symmetricKey, 
    Nonce=sendCounter, 
    AdditionalAuthenticated=channelId
}(M) 

to the remote party. The sendCounter is then incremented by 1.

The following state is kept in memory for the receiving end of a channel:

ReceivingEndState

  • channelId [16 bytes]
  • recvCounter [12 bytes]
  • zurWindowBitmask [K bytes/8*K bits]
  • symmetricKey [32 bytes]

The initial value for recvCounter is 0.

recvCounter together with zurWindowBitmask allow representing the set of counters
that will be accepted for an incoming message:

(counter > recvCounter) 
or
(
    (recvCounter - 8*K <= counter < recvCounter) 
    and 
    (zurWindowBitmask[recvCounter - counter - 1] = 0)
)

Note: We consider the lsb of the zurWindowBitmask to be bit number 0.

This zur window bitmask construction allows more freedom for messages to arrive
out of order or not arrive at all. K is some parameter that we need to choose.
A reasonable value for K is 32 bytes = 256 bits. The bigger the value of K,
the more flexibility we allow for messages to be delayed or be dropped, for the
cost of using more memory for one channel receiving end.

Upon the receipt of an encrypted message, the receiving party does the
following:

  1. Read the additional authenticated data to obtain the channelId.
  2. Attempt to decrypt the encrypted message using the appropriate symmetricKey.
    If the decryption fails, discard the message.
  3. Check the nonce:
    a. If the nonce is bigger than recvCounter:
    - Update currentCounter = nonce.
    - Shift zurWindowBitmask nonce - recvCounter bits to the left (Inserting the bit 1 from the right on the first bit shift).
    b. If nonce is in [recvCounter - K*8, recvCounter) and
    the corresponding zurWindowBitmask bit is 0:
    - Set zurWindowBitmask[recvCounter - counter - 1] = 1
    c. Else: Discard the message.
Connection teardown

Connection teardown

Consider two parties A and B with a channel ChannelAB from A to B. A uses the channel to
send encrypted messages to B. If the machine that runs B shuts down or
malfunctions, it is possible that B will lose his state for the receiving end
of the channel ChannelAB.

A might keep sending encrypted messages to B on the ChannelAB channel, but B
will not be able to read those messages because he does not have the symmetric
key required to decrypt the messages, and he does not have a recvCounter in
place to make sure the messages are not being replayed.

In this case B will indicate to A that he has no information about the channel
ChannelAB using an UnknownChannel message:

UnknownChannel

  • channelId [16 bytes]
  • randNonce [16 bytes]
  • Signature("UnknownChannel" || channelId || randNonce) [64 bytes]

B will send a new UnknownChannel message to A every time A attempts to send an
encrypted message to the channel ChannelAB.

Upon the receipt of this message, the party A will remove its sending end of
the ChannelAB channel. If A has the ability to initiate a connection, it might
attempt to setup a new connection with B.


cc @realcr I might need more details about the decryption/encryption, I haven't yet use AEAD before.

help: remove this `mut` warnings and compile errors

I tried to compile the project with the latest Rust nightly, and got lots of strange warnings that look like this:

warning: variable does not need to be mutable
  --> src/security_module/client.rs:90:31
   |
90 |         handle.spawn(sm.then(|_| Ok(())));
   |                               ^ help: remove this `mut`

warning: variable does not need to be mutable
   --> src/security_module/client.rs:115:31
    |
115 |         handle.spawn(sm.then(|_| Ok(())));
    |                               ^ help: remove this `mut`

error: variable does not need to be mutable
   --> src/timer.rs:153:19
    |
153 |             .map(|_| tm.create_client())
    |                   ^ help: remove this `mut`

It looks like a Rust bug to me, as no mut is there.

"the clippy plugin is being deprecated"

I just bumped Rust and clippy's versions.
On the new setup I get this warning when running cargo test --features=dev:

warning: the clippy plugin is being deprecated, please use cargo clippy or rls with the clippy feature

@kamyuentse : Do you know of a non deprecated way to use clippy that still allows us to pin clippy's version?

Adding keepalives between Channelers

The Channeler uses Fragmentos over UDP to communicate with other Channelers. Firewalls, NATs and routers have the tendency to shut down UDP communication if it is inactive for a while. Therefore I think that we should add some keepalive message to the Channeler. I explain below how I think we should add it.

An example use case could be as follows: Consider two nodes, C and S. C is a client behind a NAT. S is a server with a known IP address. Network configuration: C ----- NAT ------ S
Initial setup:

  • C adds S to his neighbors list as a pair of (public_key_S, ip_address_S).
  • S adds C to his neighbors list as a pair of (public_key_C, None)

C sends the first handshake message to S (Using Fragmentos, over UDP). The first stop of the message is the NAT. It creates a new entry that maps a new port to the session began from C. It then forwards the message to S. S then sends back the second message in the 4-way handshake. When the message arrives at the NAT, it looks at its mapping table and it knows to forward the message back to C. Using this technique C and S can communicate, although C doesn't have a public IP address.

If C and S don't send any messages for a while, the NAT will remove the session entry due to timeout. As a result, if S sends a message to C, the message will not pass through the NAT.

A possible solution would be to constantly send keepalive messages between C and S. Both C and S send a keepalive message to the remote side if no message was sent for a while.

The current proposed Channeler's interface is at channeler_udp.capnp. Quoting from this file:

# The data we encrypt. Contains random padding (Of variable length) together
# with actual data. This struct is used for any data we encrypt.
struct Plain {
   randPadding @0: Data;
   # Random padding: Makes traffic analysis harder.
   content @1: Data;
}

struct ChannelerMessage {
        union {
                initChannel @0: InitChannel;
                exchangePassive @1: ExchangePassive;
                exchangeActive @2: ExchangeActive;
                channelReady @3: ChannelReady;
                unknownChannel @4: UnknownChannel;
                encrypted @5: Data;
                # Nonce is a 96 bit counter, Additional authenticated data is
                # channelId.
        }
}

I propose to put the keepalive message inside struct Plain, as follows:

struct Plain {
   randPadding @0: Data;
   # Random padding: Makes traffic analysis harder.
   union {
           keepalive @1: Void;
           content @2: Data;
   }
}

Reasons for putting the keepalives inside an encrypted message:

  • They can not be faked.
  • It will be harder to distinguish between keepalives and real data packets.

I think that we should also use the keepalives to notice that a session with a remote Channeler is not active anymore. In this case we should clear the sender (symmetric key + incrementing nonce) and possibly attempt to initiate new secure channels using the 4-way handshake.

@kamyuentse: What do you think?

Stale code

After stumbling upon unlinked file during development in #215 , I created a script to find other unlinked files. Here are the results for current master:

Stale files (safe to remove):

    components/common/src/async_adapter.rs
    components/common/src/frame_codec.rs
    components/common/src/wait_spawner.rs
    components/funder/src/credit_calc.rs
    components/funder/src/freeze_guard_legacy.rs
    components/stctrl/src/bin/stverify.rs

freeze_guard_legacy.rs will be removed once #215 is merged. Please decide what to do with other files. I think it would be nice to have similar analysis for unused functions, but existing utilities (like warnalyzer) are not ready for codebases as large as offst yet.

Use sqlite3 for persistent storage

We are currently using serialization to json file (using serde) and writing to disk using the atomicwrites crate.

Cons of the current design:

  • Very inefficient, as the whole database should be written for every mutation.
  • Possible atomicity issues. sqlite3 is probably more battle tested than the atomicwrites crate.

Required steps:

  • Designing an SQL schema.
  • Refactoring Funder's mutations to contain less logic.
  • Translating Funder's mutations to SQL statements.

Multi currencies proposal (orig: Exchange rates example)

Hi there, I really like the idea of distributed ledger without proof of work. Really cool project.

I don't have much of a financial background, and I would like to better understand a scenario that would be common to me and a couple of my friends, if I was to present them the idea of the project.

If we setup a ledger between us, Alice, Bob and Charli, we would start with a 0 balance as documented. Alice provides Bob milk by 1 offst token in Brazil. Bob travels to meet Charli, which lives in Germany, and she also sells milk to him.

For how much should Charli charge for the milk? I thought of two scenarios, with some tradeoffs:

  • Have one ledger in each coin

I could setup a wallet between Alice and Bob, and another between Bob and Charli. But that means that I could never exchange money between both wallets, otherwhise I would be converting a less valuable token to a more valuable token. We would rely on social norms to avoid this, and that does not seem like a good idea.

  • Allow people to determine the amount when selling you currency by offst token

That looks like what bitcoin does, and could lead to a lot of volatibility on the token, and become a trading market on itself, if that is what I've understood.

  • Have a wallet based on a currency
    If each wallet had a token type, it would be possible to create one wallet per currency, and manage debt only between those currencies. So far, as I've learned about accounting, this seem to be what plaintext accounting does.

Introducing exchange rates between wallets seems complicated for the system tho, and seems enough to separate the ledgers and repay the person to clear your debts. Or you could buy some currency using one ledger if the other person is willing to accept as well, selling the "good" using the system.

I've been looking at the project for a very short time, but I've didn't see any comments regarding different currencies and exchange rates. Is this something to be considered by the project at some point?

derive_symmeric_key might panic due to invalid public key

At crypto/dh.rs, inside derive_symmetric_key there is the following code:

let shared_secret_array = match key_material_res {
    Ok(key_material) => key_material,
    _ => unreachable!(),
};

I think that we might be able to reach the unreachable code. (I wrote this code!) This might allow an attacker to acheive DoS, in case an invalid public key is provided during Diffie Hellman.
I think that a reasonable solution would be to change the function signature from:

pub fn derive_symmetric_key(&self, public_key: &DhPublicKey, salt: &Salt) -> SymmetricKey {

to

pub fn derive_symmetric_key(&self, public_key: &DhPublicKey, salt: &Salt) -> Result<SymmetricKey, ???> {

And return an Error on the case where we currently have unreachable!().

@kamyuentse : This could affect current Channeler code, so I assign this to you.
Tell me what you think.

Proposal: RequestPath interface for the Networker

This is a proposal for modification to the Networker's service interface (Against IndexerClient, Funder, AppManager). This modification to the Networker's interface is done in order to reflect the work it takes to set up a secure path to a remote Networker.

Secure paths

A secure path is an abstract idea used by the users of the Networker. Any component that takes service from the Networker may request a secure path to a remote node by providing a neighbors' route to the node. A secure path allows sending encrypted messages to a remote node.

A secure path is mapped to a neighbors route together with a channel sender. The channel sender allows to encrypt messages with the correct incrementing nonce. The neighbors route is necessary for routing the message to its destination by other Networkers on the way.

All the secure paths to a remote node use the same channel sender (with the same encryption key and the same incrementing nonce). However, it is possible to open several distinct secure paths that use different routes to the same remote node.

Secure paths should be used like a TCP connection. A message sent over a secure path will be retransmitted as necessary by intermediate Networkers. Therefore messages should not be retransmitted into a secure path.

Using the RequestPath interface

With the new interface, using the Networker's service from a component (IndexerClient, Funder or AppManager) will be done as follows:

Component <---> Networker

  1. The Component sends a RequestPath message to the Networker.
    The Networker then checks if a current sender to the remote Networker exists. If so, an mpsc::Sender is sent back through the response_sender. If not, the Networker attemps to run a 4-way handshake against the remote Networker. If the 4-way handshake is completed successfuly, an mpsc::Sender is sent back to the component. If a failure occurs, a None is sent back to the component.

  2. The component obtains an mpsc::Sender sender. A message can be sent by sending a RequestSendMessage through the sender to the Networker.

  3. The Networker receives a RequestSendMessage. The RequestSendMessage contains a field: response_sender: mpsc::Sender. the Networker encrypts the message using the relevant key (This key was set up earlier during the 4-way handshake). Then this message is sent through a token channel. When a response is obtained from the token channel (either a success or failure), the Networker sends the response (success or failure) through the given mpsc::Sender.

  4. The Component obtains the ResponseSendMessage for the sent message.

Closing a secure path

A secure path mpsc::Sender<RequestSendMessage> may be closed from the Component side by closing the sender using Sink's close method This will signal to the Networker that the secure path is not used anymore.

A secure path can also be closed from the Networker's side by Dropping the receiver end. The Component will be notified of this when he tries to send a message through the secure path's sender.
The Networker will close his receiving end of the secure path in the following cases:

  • An Unknown Channel message was received from the remote Networker. This will cause all the secure paths to that destination to be closed.
  • Messages sent through this route were not acked for a very long time. This will cause a single secure path to be closed (The one that uses this neighbors route).

Cleanup warnings

The current building process has lots of warnings that occur due to changes in Rust.
Some examples:

warning: use of deprecated item 'proto::proto_impl::channeler::channeler_capnp::exchange_passive::Builder::borrow': use reborrow() instead
  --> src/proto/proto_impl/channeler.rs:63:50
   |
63 |         write_signature(&self.signature, &mut to.borrow().init_signature())?;
   |      
warning: use of deprecated item 'bytes::Buf::get_u64': use get_u64_be or get_u64_le
   --> src/proto/proto_impl/common.rs:118:22
    |
118 |     to.set_x5(reader.get_u64::<BigEndian>());
    |                      ^^^^^^^

and

warning: this feature has been stable since 1.26.0. Attribute no longer needed
 --> src/lib.rs:4:12
  |
4 | #![feature(i128_type)]
  |            ^^^^^^^^^
  |
  = note: #[warn(stable_features)] on by default

warning: this feature has been stable since 1.26.0. Attribute no longer needed
 --> src/lib.rs:6:12
  |
6 | #![feature(conservative_impl_trait, universal_impl_trait)]
  |            ^^^^^^^^^^^^^^^^^^^^^^^

warning: this feature has been stable since 1.26.0. Attribute no longer needed
 --> src/lib.rs:6:37
  |
6 | #![feature(conservative_impl_trait, universal_impl_trait)]
  |                                     ^^^^^^^^^^^^^^^^^^^^

@kamyuentse : I might take this one myself today, I can't see the compilation errors anymore because there are so many warnings (:

use of unstable library feature 'proc_macro'

PR #68 stopped compiling with a somewhat unclear compilation errors. Here is an example:

error[E0658]: use of unstable library feature 'proc_macro' (see issue #38356)8: proc-macro2                                                                                                                        
  --> /home/real/.cargo/registry/src/github.com-1ecc6299db9ec823/proc-macro2-0.4.8/src/unstable.rs:33:40
   |
33 |     let works = panic::catch_unwind(|| proc_macro::Span::call_site()).is_ok();
   |                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: add #![feature(proc_macro)] to the crate attributes to enable

It is possible that some library has recently updated, However I am not sure which one, and what is the cause for this problem. I sent a message at issue #38356:
rust-lang/rust#38356 (comment)
Though I'm not fully sure it is related.

The same problem doesn't happen on master, I think that this might be related to the use of #[async] style syntax.

@kamyuentse, @juchiast : Do you have an idea of how to fix this?

RequestSendFundsOp should only contain next nodes on route

Summary

Currently RequestSendFundsOp always contains the full route from the buyer to the seller. To (somewhat) protect the buyer's privacy, we propose to remove the nodes from the beginning of the route. Those nodes are not required for the correct operation of the protocol, and they expose the identity of the buyer.

Example: Consider the following network layout:

B -- C -- D -- E

Currently, a RequestSendFundsOp message from B to E contains the route: B -- C -- D -- E.
We denote the contents of the route in the RequestSendFundsOp message received by the following nodes:

  • B: B -- C -- D -- E.
  • C: B -- C -- D -- E
  • D: B -- C -- D -- E.

In other words: when the message is forwarded from B to C, it still contains the full B -- C -- D -- E. Next, when C forwards the message to D, it still contains the full route: B -- C -- D -- E. Finally, when the message arrives at D, is still contains the full route: B -- C -- D -- E. This reveals to E the public key of the buyer: B, hence violating the privacy of B.

Instead, we propose that when a node receives a RequestSendFundsOp message, the message will contain as a route only the next nodes, and not include the node itself and the previous nodes. Continuing the example,

  • B will receive a RequestSendFundsOp message with the route C -- D -- E.
  • C will receive a RequestSendFundsOp message with the route D -- E.
  • D will receive a RequestSendFundsOp message with the route E.

Privacy implications

This modification should provide some sort of privacy for the buyer (But not for the seller). Note that this will not provide "perfect" privacy for the buyer, for the following reasons:

  • The Index server might know the identity of the buyer.
  • A few colluding nodes might be able to find out the identity of the buyer.

That said, the proposed modification is a "quick win" for increasing the buyer privacy without too much work.

In the future, if this becomes important enough, we might be able to consider techniques like onion routing, where every node is given only the identity of the next node to forward a RequestSendFundsOp, in encrypted form.

Instructions

The proposed modification does not require changing the structure of RequestSendFundsOp or any serialization code.

The major changes required are:

  1. offst-proto signature buffers
  2. offst-funder logic (The part that deals with incoming and outgoing Request messages).

Part (1) Currently the ResponseSendFundsOp contains the following capnp description:

struct ResponseSendFundsOp {
        requestId @0: Uid;
        destHashedLock @1: HashedLock;
        randNonce @2: RandNonce;
        signature @3: Signature;
        # Signature{key=destinationKey}(
        #   sha512/256("FUNDS_RESPONSE") ||
        #   sha512/256(requestId || sha512/256(route) || randNonce) ||
        #   srcHashedLock || 
        #   destHashedLock || 
        #   destPayment ||
        #   totalDestPayment ||
        #   invoiceId
        # )
        # ...
}

Consider the signature description above (in comment). The signature currently contains sha512/256(route). This part needs to be removed, as with the proposed modification nodes later on the route will not be able to calculate this value. Therefore the route should not be included at all inside the signature. The new signature should be:

   # Signature{key=destinationKey}(
        #   sha512/256("FUNDS_RESPONSE") ||
        #   sha512/256(requestId || randNonce) ||
        #   srcHashedLock || 
        #   destHashedLock || 
        #   destPayment ||
        #   totalDestPayment ||
        #   invoiceId
        # )
        # ...

This new signature form should be updated in all the comments that contain signature description:

  • funder.capnp in offst-proto
  • doc/docs/atomic_proposal.md
  • fuder/signature_buff.rs in offst-proto.

The code that computes this signature could be found in funder/signature_buff.rs in offst-proto. Make sure to modify all the places that calculate the signature. I think that the code in signature_buff.rs contains some kind of code duplication for the computation of the signature. If you can refactor this part while you are there it could be an extra win.

After this part is finished, cargo test should work correctly.
At this point the full route is still forwarded in the tests, but the signature does not include the full route any more.

Part (2) offst-funder logic

I think that the files that require modification are:

  • funder/src/mutual_credit/incoming.rs (process_request_send_funds()).
  • (I'm almost sure outgoing.rs does not require any modification.)
  • funder/src/handler/handle_friend.rs (Cutting one node from the route)
  • funder/src/handler/handle_control.rs (Cutting one node from the route)

Stuff that should be changed:

  • During verification: Instead of searching ourselves and our friend on the route, assume that we are the first on the route (But not included), and the friend showed up earlier (also not included). For example, if the full route is B -- C -- D -- E -- F and we are C, we should only see D -- E -- F.

  • Removing the first node from the route at handle_friend.rs. This should be done at the same place where RequestSendFundsOp.left_fees is decreased, at the end of handle_request_send_funds().

  • Removing the first node from the route given at control_create_transaction_inner() in handle_control.rs right before we push the request into the pending user requests queue. We need to do this because now the rest of the funder code now expects routes that do not begin with our own public key. Instead, the route should begin with the public key of the next node.

Other notes

  • The Funder's code is a bit strange, because any mutation done on the Funder data must be atomic. This is intentional.

  • There is reasonably good test coverage for the code mentioned above, so the tests should help you be sure you are on the right track. Integration tests are really slow, and you don't need to run them all the time. You can run only the relevant tests for a specific crate, like this: crate test -p offst-funder.

  • Be sure to send me a message if things get more complicated than the scope described in this issue.

kcov tweaking

After #207, I tried tweaking kcov settings further on, to understand how they work. I was running kcov on my local machine, using perf stat ./kcov-script.sh.

--{include,exclude}-{path,pattern}= options are used for filtering source files. Per kcov man page, they take comma-separated list of patterns. When no such options are present, all files are used (that includes dependencies downloaded in $HOME/.cargo). Include and exclude are filtering list of files kcov finds in DWARF informations. They kinda work like this (Rust pseudocode):

fn include(files: Vec, text: String) -> Vec {
    files.iter().filter(|x| x.contains(text)).collect()
}
fn exclude(files: Vec, text: String) -> Vec {
    files.iter().filter(|x| !x.contains(text)).collect()
}

{include,exclude}_path are much slower than {include,exclude}_pattern. --include_path=components adds 60 seconds to kcov process on my machine, and it would probably be more on Travis.

Since we use include_pattern=components, only files which path contains components are picked. If any dependency of offst ever happens to have components directory, its test coverage would influence ours. That's why --exclude-path=/usr,~/.cargo may be a good idea. --exclude-pattern=/usr/,/.cargo/ may be an even better one (see paragraph above), as long as we don't create folder called "usr" in offst codebase.

--verify flag makes kcov 3-4 times slower, but it was needed to prevent it from segfaulting when working with Rust binaries. I tried removing it and nothing broke. I'm using the latest version of kcov (CI is not). This may be worth investigating.

kcov runs single-threaded. I tried using GNU parallel like this:

find \
    target/${TARGET}/debug \
    -maxdepth 1 -executable -type f \
    | parallel \
    -j0 \
    'kcov ${FLAGS} target/kcov {}'

-j0 means "spawn as many threads as there are CPUs". This was not a good idea - kcov crashes a lot when multiple processes try to access target/kcov at once.

So, given these pieces of information, I would suggest trying:

for exe in ${exes}; do
    ${HOME}/install/kcov-${TARGET}/bin/kcov \
        --exclude-pattern=/usr/,/.cargo/ \
        --include-pattern=/components/ \
        target/kcov \
        ${exe}
done

Or maybe --exclude-pattern=/.cargo/ in case we ever create usr directory. Or maybe just --exclude-path=/usr --exclude-pattern=/.cargo/, it is costly, but it still should be better than what we have now, especially without --verify.

For now I'm just writing what I know, asking for opinion.

invalid arithmetic operator in before-script.sh

I noticed some syntax error that happens during the travis build and test.
The source of the problem is probably before-script.sh, but I couldn't yet fix it. You might have an idea.

I couldn't make new pull requests pass the cargo clean && cargo clippy on linux for some reason (They did pass on Mac though). I thought that it might be related to this issue.

I include the output from the Travis build attempts. Note that this error also occurs on successful builds.
@kamyuentse : You might have an idea. I think that this might be related to some recent modification I made to before-script, with the CLIPPY_VERSION variable.

before_script
0.03s$ travis/trusty/before-script.sh
+CLIPPY_VERSION=0.0.206
+[[ -f .cargo/config ]]
+[[ ! -d .cargo ]]
+mkdir .cargo
+echo '[target.x86_64-unknown-linux-gnu]'
+echo 'linker= "gcc-6"'
+[[ -f /home/travis/.cargo/bin/cargo-clippy ]]
++cargo clippy --version
+[[ 0.0.206 -ne 0.0.206 ]]
travis/trusty/before-script.sh: line 17: [[: 0.0.206: syntax error: invalid arithmetic operator (error token is ".0.206")

EDIT:
Relevant code from before-script.sh:

echo "[target.$TARGET]" > .cargo/config
echo "linker= \"$CC\"" >> .cargo/config

if [[ -f "$HOME/.cargo/bin/cargo-clippy" ]]; then
    if [[ "$(cargo clippy --version)" -ne "$CLIPPY_VERSION" ]]; then
        cargo install --force clippy --vers "=$CLIPPY_VERSION"
    fi
else
    cargo install clippy --vers "=$CLIPPY_VERSION"
fi

Rethinking the error management system

Currently, the CSwitch uses the basic handwritten Error type to manage the error, I admit that's the most flexible way, but it would make us spent more time to write the code here if we want to trace the source of the error, or we got a useless error.

For example, in the crypto, I notice we use () as the error in many places. But when the program runs into trouble, how to find the where is the exact location? If we leave this implementation, the caller must handle this error once the error happened, and assign a proper Error variant for it, this work should be done for each caller.

Another example is the Channeler, in the Channeler, we have some submodule, each of them may run into trouble. If we want to write a recoverable system, I think we should divide the error into:

  • fatal ── we should close the whole system.
  • recoverable ── which means that module is already broken, we should find a way restart the module, if we can't restart this module, it would be a fatal error, we should close the whole system.
  • ignorable ── an error happened, but it does not harm the whole module. For example, in futures 0.1, when polling a stream and it returns an Error, it doesn't mean that the stream terminated, check rust-lang/futures-rs#206 for more details.

The partition above might not accurate, just my idea.


If this is reasonable, we may discuss it, and add more things here.

Uuid should be Uid

uid.rs originally contained implementation of random generation of unique ids.
It was later renamed to uuid.rs, and the following comment was added:
///! A rough implementation of UUID v4.

I believe that this could cause confusion for the reader, as uuid.rs does not implement UUID v4, and does not intend to do this. I am not sure when was the uid.rs -> uuid.rs change happened, but using the logs I can confirm that the original name of the file was uid.rs:

$ git log | grep uid
    Added uid.rs for generation of Uids.

think that we should remove the comment about UUID v4, and possibly rename it back to uid.rs to avoid confusion. I don't know if uuid.rs is currently in use by the Channeler code, so I did not make any modification yet.

Index servers are too optimistic (fees not accounted for)

Index servers

Index servers do mainly two things:

  1. Collect information about the network: For every node, index servers collect his current available credit capacity with his direct friends.
  2. Answer queries for routes of certain capacity.

Problem with current implementation

The current implementation of Index servers is too optimistic, because index servers do not take into account the fees for sending credits along routes.

For example, with the current implementation, if a node B wants to send 10 credits to a remote node E, the following route may be proposed:

B -- C -- D -- E

Where the available capacity is as follows:

  • B -> C: 10
  • C -> D: 10
  • D -> E: 10.

A route with exactly those capacities for sending credits is not sufficient for passing 10 credits, because fees needs to be paid to the intermediate nodes. Therefore, at least the following available capacities are required:

  • B -> C: 12
  • C -> D: 11
  • D -> E: 10

The implementation of the Index servers needs to be updated to take into account the payment fees to mediators.

Relevant code

The relevant code is at: components/index_server/src/graph/simple_capacity_graph.rs, which relies on the implementation at bfs.rs.

The current implementation relies on running a BFS algorithm to find a path of certain capacity from a source node to a destination node. We need to take into account the fees to the transaction mediators in this search algorithm.

Other relevant code is somewhat far away, at components/funder/src/credit_calc.rs. This is the piece of code that contains the actual calculation for fees to mediators during a transaction. The crate offst-index-server should not have the crate offst-funder as a dependency, so maybe it could be a good idea to move credit_calc.rs to offst-proto, possibly now or in the future.

Proposal for solution algorithm

It might be possible to start a graph search algorithm that goes backwards (From the destination back to the source). The search algorithm can be somewhat like BFS, but we need to make sure that doing something like this is actually sound, and we can't possibly miss valid routes or return wrong routes.

How to work on this

This is a task that requires some prior research. Please write a short proposal of what you intend to do before you start coding, hopefully I might be able to say something useful that could help you out.

capnp_conv review + cleanup

Help required

capnp-conv was written very hastily, and some help is required with improving the code:

  • Code review + refactoring suggestions
  • A more extensive test suite, possibly using Quickcheck or proptest. I'm not sure which one to pick.
  • Better error messages
    • Marking the correct span when an error occurs.
    • Providing extra information in unimplemented!() calls.
  • Extra documentation

What is capnp_conv

capnp_conv is a procedural macro mechanism allowing to automatically derive capnproto serialization and deserialization glue code between Rust structs and capnp structs.

Example for usage (From offst-proto):

#[capnp_conv(crate::funder_capnp::request_send_funds_op)]
#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize)]
pub struct RequestSendFundsOp {
    pub request_id: Uid,
    pub src_hashed_lock: HashedLock,
    pub route: FriendsRoute,
    #[capnp_conv(with = Wrapper<u128>)]
    pub dest_payment: u128,
    #[capnp_conv(with = Wrapper<u128>)]
    pub total_dest_payment: u128,
    pub invoice_id: InvoiceId,
    #[capnp_conv(with = Wrapper<u128>)]
    pub left_fees: u128,
}

The user can then invoke: request_send_funds.to_capnp_bytes() or RequestSendFunds::from_capnp_bytes(...)

The design of capnp_conv is inspired by proto_conv.

capnp_conv is currently being used through the whole Offst codebase, mostly in offst-proto.

Upgrade to latest version of the ring crate

We currently use an old version of the ring crate, due to problems with deterministic testing. (See #64).
It seems like other parts of the ring API has changed, and some work might be needed to use the latest version of ring. Most of the modifications should be at the offst-crypto crate.
If no other solutions comes up, we might need to give up the determinism in some of our cryptographic unit tests (For example, in offst-secure-channel). Relevant issue in the ring respository: briansmith/ring#661

Implementing Index Server multi route searching

Summary

When paying using an Offst node, a route to the destination node is required. Offst nodes obtain routes by sending a request to an index server. Currently the algorithm used at the index server is very naive, and might fail in some cases. We want to improve this algorithm.

Note: This issue continues issue #195.

Multi routes

A multi route between B and E is a set of routes (must be disjoint?) between B and E. For example:

B -- C -- E
B -- D -- F -- E
B -- G -- H --E

is a multi route between B and E.

To be a suitable multi route, the multi route between B and E should allow pushing enough credits from B to E. The index server's search algorithm must also take into consideration the fees B has to pay mediator nodes along the way.

Rates and Fees

Every node can specify a rate for forwarding payments for any of his immediate friends. We denote Rate{B,C} to be the rate the node B set for the node C. (Only B controls this value).
Rates are of the form Rate {add: u32, mul: u32}. Using a Rate, one can calculate the fee for sending x credits as rate.calc_fee(x) == (mul * x / 2**32) + add

Example: Consider the following network topology:

B -- C -- D -- E
      \-- F
  1. If B sends C x credits along the route B -- C, no fees will be paid.
  2. If B sends D x credits along the route B -- C -- D, C will be paid Rate{C,B}.calc_fee(x)
  3. If B sends F x credits along the route B -- C -- F, C will be paid Rate{C,B}.calc_fee(x)
  4. If B sends E x credits along the route B -- C -- D -- E, C will be paid Rate{C,B}.calc_fee(x), and D will be paid Rate{D,C}.calc_fee(x).

Available information for searching

The index server maintains capacity edges of the form:

pub struct CapacityEdge<C, T> {
    pub capacity: CapacityPair<C>, // send and receive capacity pair.
    pub rate: T,
}

For each pair of friend nodes. These edges are directed. This means that for two nodes B -- C, the index server will keep a CapacityEdge for the edge (B,C) and another one for the edge (C,B). Each edge represents the point of view of one of the nodes.

To get the "actual" send and receive capacity, one should take, for example, the minimum of send capacity of one side together with the receive capacity of the other side. This method should protect against one node reporting an inflated capacity to the index server.

Exclude edge option

The algorithm should allow searching the whole graph with one excluded edge. This is useful to be able to search for cycles. An example use case would be letting a node rebalance his debts. The way to do this is to find a cyclic route from a node to himself, and let him push credits along this route.

For example, consider the network layout:

 /--...--\
B -- C -- D

Where the following pairs of nodes are friends: (B,C), (C,D). We also know that there is some route between B and D that doesn't include the edge (C,D).

Assume that C wants to move his debt with D to the node B. This can be done by letting C pay to himself along the route: C --> D --> ... -> B --> C.

Without the edge excluding feature, if C naively asked for a route that goes from D to C, he would have gotten the route D -- C, which is not suitable for debt rebalancing. However, if C uses the edge exclusion feature, he can ask to get a route from D to C that doesn't go through the directed (D,C) edge. This forces the index server to come up with a route that will allow C to rebalance his debts.

Thumb rules for expected results

  1. The search algorithm should find reasonably cheap multi-routes between pairs of nodes, reasonable quickly.

  2. Single routes (Multi routes with only one route) are preferred over multi routes, unless (3).

  3. It is preferred that every route in the multi route is not "saturated". For example: If we want to push 20 credits, and the index server returns two routes that have capacity of 10 credits each, we get a very tight situation (Every small change in the two routes might shut our opportunity to send funds). However, if we get three routes, each having 10 free credits, we have more space for changes. I have no idea how to put this into the algorithm. If it turns out difficult, it is not required.

In the current implementation of stctrl, when a multi-route is obtained, we try to take some capacity from each of the routes while staying as far as possible from saturation (Using the metric of max(distance from saturation)). You can check it here

There are some things I'm not sure about here, this is why I named it "thumb rules" and not "rules":

  • How to get multiple multi-route results?
  • How to make sure we get safe to use multi routes, (where non of the routes is saturated, as described in (2)).
  • Should routes in a multi-route always be disjoint?

The most minimal solution should be able to return one multi-route whenever a valid one can be found.

Relevant code

I think that this has became a pretty self contained task. The code that should be changed is at:
components/index_server/graph. bfs.rs probably needs to change into some kind of advanced dijkstra, and in simple_capacity_graph.rs we should change get_multi_routes() and get_multi_route().

bill-splitting feature

Posted on behalf of duxovni:

One feature I think it'd be really handy for the app to have is a UI for splitting bills, where a group of friends have a shared expense (split evenly or unevenly) and one of them fronts the money for it and gets IOUs from everyone else. That seems like something that'd fit really easily into this design and be very useful to a lot of people.

Some thought is required on how to make this work:

  • Is it possible to implement this safely using the current Offst protocol?
  • How should the UI for this feature look like?

Changing algorithm used for hash locks

Hash locks are being used in the protocol to allow atomicity of payments. Currently hash locks are implemented using SHA512/256.

It might be safer to use a different algorithm, for example bcrypt (As suggested in issue #196). On the other hand, it might degrade performance. I'm not yet sure what is the correct thing to do here.

The implementation can be found in components/crypto/src/hash_lock.rs
Note that changing the implementation might require changing the internal structure of HashedLock (For example, if we want to add hash salt).

Exponential backoffs for all connection attempts

There are some parts in offst that attempt connections:

  • Relay client
  • Channeler
  • Index client

Currently each of those have a very basic backoff mechanism for reconnection after a failed connection attempt. This might cause DoS on a server.

We should change the backoff mechanism to exponential backoff, and find reasonable ways to test the new exponential backoff logic. Maybe there is a way to isolate this logic and reuse it in all clients that connect to servers in offst.

Add Android builds to CI

Offst's stcompact binary is designed to work on mobile devices, specifically on Android.

We want to compile it for the following architectures:

  • aarch64-linux-android
  • armv7-linux-androideabi
  • i686-linux-android

More specifically, we want the CI to compile for those architectures during testing, and create a release binary when a new version tag is pushed to this repository.

Currently the automatic CI build system compiles and tests Offset for Windows, linux and macos.
Building for the Android architectures mentioned above is probably a bit different, because cross compilation is required.

I managed to perform cross compilation successfully on my machine (Ubuntu 18.04) by following the instructions from mozilla's website, with some minor modifications:

  • Install android sdk
  • Install ndk
  • export NDK="/home/real/Android/Sdk/ndk/20.1.5948944"
  • Add symbolic link: ln -s $NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android26-clang $NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android-clang. Should add a similar symbolic link for the other two architectures.
  • Add $NDK/toolchains/llvm/prebuilt/linux-x86_64/bin to $PATH environment
    variable
  • Create ~/.cargo/config, containing:
[target.aarch64-linux-android]
ar = "/home/real/Android/Sdk/ndk/20.1.5948944/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android-ar"
linker = "/home/real/Android/Sdk/ndk/20.1.5948944/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android-clang"

[target.armv7-linux-androideabi]
ar = "/home/real/Android/Sdk/ndk/20.1.5948944/toolchains/llvm/prebuilt/linux-x86_64/bin/arm-linux-android26-ar"
linker = "/home/real/Android/Sdk/ndk/20.1.5948944/toolchains/llvm/prebuilt/linux-x86_64/bin/arm-linux-android26-clang"

[target.i686-linux-android]
ar = "/home/real/Android/Sdk/ndk/20.1.5948944/toolchains/llvm/prebuilt/linux-x86_64/bin/i686-linux-android26-ar"
linker = "/home/real/Android/Sdk/ndk/20.1.5948944/toolchains/llvm/prebuilt/linux-x86_64/bin/i686-linux-android26-clang"
  • Finally, create a release using cargo build --target aarch64-linux-android --release

I need some help with making this work on the travis CI. My current gap in knowledge is how to install the android SDK and the NDK on the travis CI. Alternatively, maybe travis CI has special support for this case.

offst-crypto: Rename crypto_rand.rs to rand.rs

crypto_rand.rs is an unfortunate choice of name for the random utils module inside offst-crypto crate.

For example, this is what current imports look like:

use crypto::crypto_rand::{CryptoRandom, RandValue};

A better name for this module might be just rand, which should result in imports of this form:

use crypto::rand::{CryptoRandom, RandValue};

The crypto crate is used in most of offst crates. I suggest to begin with modifying the lowest crates first: offst-common and offst-proto, and only then move on to the rest of the crates.

At any time, running cargo test should show any failures during the renaming. The more specific cargo test -p offst-XYZ allows to test a specific crate (Given that all dependencies compile too).

This issue does not require great familiarity with the Offst project, and I think it can be solved by Rust beginners too. Guidance will be given to anyone who wants to solve this. Please send a message if you plan to work on this issue.

Timer CI failing.

---- timer::tests::test_timer_basic stdout ----
	thread 'timer::tests::test_timer_basic' panicked at 'assertion failed: start.elapsed() < dur * 17 * 12 / 10', src/timer.rs:163:21

This happened and make the CI failing in past submissions. Always occurred on the macOS.

Macro for deriving RandGen for some structs

The code at https://github.com/freedomlayer/offst/blob/master/components/crypto/src/rand.rs is very repetitive. Example:

impl RandGen for Salt {
    fn rand_gen(crypt_rng: &impl CryptoRandom) -> Self {
        let mut res = Self::default();
        crypt_rng.fill(&mut res).unwrap();
        res
    }
}

impl RandGen for InvoiceId {
    fn rand_gen(crypt_rng: &impl CryptoRandom) -> Self {
        let mut res = Self::default();
        crypt_rng.fill(&mut res).unwrap();
        res
    }
}

// ...

We can create a macro that does this automatically, possibly a macro that that can be used like this:
derive_rand_gen!(InvoiceId);

This issue is suitable for beginners with Rust and with the Offst project.

Safe erasure of secret data

NOTE: If the protocol required changes, put this proposal aside, and fix the protocol first.

The Encryption Algorithm is fixed during the design of CSwitch, in other words, this is a part of the CSwitch's protocol, which is the starting point of this proposal.

The design of this proposal has the following goals:

  • Decouple the protocol part to the details of the implementation.
  • Make a strong trait constraint to the crypto primitive types for security consideration.

Currently, we have the following crypto primitive types:

Name Size impl traits
DhPrivateKey \ \
DhPublicKey 256 bits Clone, Group1
PublicKey 256 bits Clone, Group1, Group2
Signature 512 bits Group1
Salt 256 bits Clone, Group1
RandValue1 128 bits Clone, Group1
HashResult2 256 bits Clone, Group1, Group2
Nonce 96 bits Group1

Note:

  • Group1: Including AsRef<[u8]>, Deref<Target=[u8]> and DerefMut<Target=[u8]> for reading and writing the underlying bytes conveniently.
  • Group2: Including PartialEq, Eq and Hash, which means those types may be the key of a HashMap, HashSet, etc.

1: Should we rename this type to RandNonce, because I notice that we always use the rand_nonce in where we hold this value.

2: The name of this type seems to obey the Rust's naming conventions, anyone has an idea about the name here?

Consideration about the Clone

For deriving the symmetric keys, each party needs to save a copy of those types during the handshake: DhPublicKey, PublicKey, Salt. For calculating the channel identifier, they need the DhPublicKey, RandValue. They need impl Clone, but I am not very sure whether we should do that. This information should be consumed and erase as soon as possible.

Drop the secret data safely

For the type mentioned above, I think we should manually implement Drop for them, which erase the memory block where holds these value to ZERO. This will be done by the following code:

impl Drop for Nonce {
    fn drop(&mut self) {
        unsafe {
            for b in &mut self.0 {
                ::std::ptr::write_volatile(b, 0);
            }
        }
    }
}

Traits required by testing

Some traits are required when performing tests, i.e. Eq, PartialEq .etc. I think this can be done by #[cfg_attr(feature = "test", derive(Eq, PartialEq)))], this trick alse can be use for other types.


Comments and fill in the other details are welcome.

DummyRandom does not work with ring v0.13.0-alpha5

In the latest update to ring v0.13.0-alpha5 we get the following compilation error:

error[E0277]: the trait bound `crypto::test_utils::DummyRandom<R>: ring::private::Sealed` is not satisfied
  --> src/crypto/test_utils.rs:23:14
   |
23 | impl<R: Rng> SecureRandom for DummyRandom<R> {
   |              ^^^^^^^^^^^^ the trait `ring::private::Sealed` is not implemented for `crypto::test_utils::DummyRandom<R>`

See also issue briansmith/ring#661

I see three options to fix this:

  1. Make our own trait that wraps SecureRandom. We can call it RandomGenerator. All of our code will work directly with RandomGenerator. This way we will not be affected by further changes to Ring's interface.
  2. Hope that we can get DummyRandom into Ring.
  3. Use FixedSliceRandom from Ring. However, I think that the interface of this random generator is very uncomfortable, because we have to predict exactly the amount of random bytes that will be requested every time.

Currently I think that option (1) is our best bet. @kamyuentse : What do you think?

Use structopt crate for command line programs

We currently use the clap crate for command line parsing. clap uses strings to refer to arguments, which is not checked at compile time. The structopt builds over clap and allows to define structs which automatically turn into command line arguments.

This should affect code in:

  • offst-bin (All binaries)
  • offst-stctrl (The stctrl binary).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.