Open
Conversation
Codecov Report
@@ Coverage Diff @@
## master #709 +/- ##
==========================================
- Coverage 47.53% 47.52% -0.01%
==========================================
Files 373 373
Lines 19391 19391
Branches 1784 1784
==========================================
- Hits 9217 9216 -1
- Misses 10159 10160 +1
Partials 15 15
Flags with carried forward coverage won't be shown. Click here to find out more.
Continue to review full report at Codecov.
|
Contributor
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Transfer Cards V2
Presently there is no way to check transfer account limits offline. We need to add the ability to set limits which work completely when offline, and which match the logic we employ online so transactions don't fail at synchronization time. This is all based on the Mifare Ultralight EV1
Design Considerations
Risks
Per-Card Limits
To implement per-card limits, we need to store two things on the card: What the limit is, and where the user currently falls within that limit.
High-Level Goals
Stored Items
To make this work, there's a few different data types we need to consider and keep track of
Sizes
Note: We don't have to worry about storing the balance, since the balance is already stored
Schema
Now that we've established what we want to do, how large each component is, and how much space we have to work with, we need to consider our data schema. How much do we want to arrange these elements on the card to convey meaning? First we should establish limit types.
limit types:
For limit types, something can either be a
count limit, which is like "number of transactions allowed per week", or avalue limit, which would be "how much you're allowed to spend in a week". Therefore, for this piece of information we can allocate just one bit.Next we want to think of timelines on which we might want to set these limits. Daily, Weekly, Biweekly (every two weeks), Monthly, Bimonthly (every two months), Quarterly, Yearly. This is 7 bits, so it fits nicely in an unsigned 3 bit integer. With a one bit limit-type, and 7 bits for timespan types, the entire limit type fits neatly into an into a single byte.
Therefore, a weekly value limit would be written as
10000010, where the first1connotes that it's a value limit, followed by0000010(or 2), connotingweekly!limit type formats
You may have noticed in sizes that
countis half the size ofcurrency amount. This is because the highest number of transactions we're ever going to want to limit is likely much smaller than the highest amount of currency, and we want to save as much space as possible. One thing this does though, is forces us to be a bit more careful with memory allocation. Since aCount Limitwill use less overall space than aValue Limit, therefore when parsing our memory we have to be a bit more clever than to just chop up the string by bits!For
Count Limit, the first 8 bits are going to be the limit type itself (see above), followed by two bytes for the limit count (number of transactions allowed), and another two bytes for how much of that count has already been consumed. The exact same applies forValue Limittypes, but the next two sections of memory will be 3 bytes long (to connote currency rather than a count). The below example is a continuation of above, but will represent "A weekly value limit of 500, 250 has already been spent this period"last updated:
The entire concept of limits in this system is based on having a non-rolling timeline in which there's restrictions placed on cardholders' spending activities. That is to say that the limits are essentially based on a "calendar week". I.e. you can spend $5 on Monday and $5 on Friday, that will not exceed your limit, but the counter will be reset Sunday at Midnight-- you can spend $10 the following Monday even though it's within a week from the $5 on Friday). The way this works is to have the "limit used" section in the "Limit Types" memory block.
When a cardholder transacts with a vendor, they'll check the "last updated" block of memory. This is the number of days since the start of the program since the vendor last used their card. If (for a weekly-limit) that date is in the same week as the current week, then check their transaction against the limit data on the card as they exist at the present. If the date is in a subsequent week, zero-out all the "limit used" sections in the cards' memory and work from there. If the "last updated" date is higher than the current date, raise alarms since that implies tampering with the card by the previous vendor!
hash
Each individual phone will have its own key (given by the backend, referred to from here on as "phone key"), as well as a shared key which is set on an organisation-level which will live on every vendor phone ("organisation key"). When a transaction happens, the entire contents of the card (including balances, excluding hashes) will be fed into a hash function twice-- once with the phone-key appended to the message (let's call this the "vendor hash"), and again with the organisation key (let's call this the "organisation hash"). Then the second 10 bytes of the vendor hash, and the first ten bytes of the organisation hash will be concatenated, and that will be the hash which will be written to the card.
At read-time, the entire card contents will fed through the hash function too, but this time only using the organisation key. Then the validity of the hash will be checked against the second ten bytes of the hash on the card. The reason for only half of the hash being used to verify at read-time is that vendors don't know each other's phone-keys.
When transactions are synchronized, the app is to send both the pre-transaction and post-transaction card contents to the backend along with the transaction data. This is to ensure that the entire state of the cards in the wild are exactly what we expect them to be. This would be done through the backend checking the hashes by testing it using both the phone key of the vendor reporting the transaction, as well as the organisation key.
This also comes with the benefit of knowing how much has been spent on the cards which hasn't been synchronized yet (I.e. if I spend at at vendor 1, 2, 3, and 4, then vendor 4 synchronizes with the backend, I can infer how much was cumulatively spent at vendors before).
There are a few reasons for this somewhat convoluted setup:
It should be made clear, this system places trust in the hands of the vendors. The main design goal is to ensure that if somebody isn't using the system as designed, we'll be able to identify who they are.
versioning
We want to be able to make sure that each card has the newest version of its limits. That is, if the backend wants to adjust a user's limits (or set their initial limits), the system should make sure that they're put in place as soon as a cardholder interacts with a recently synchronized mobile app. Do do this, I propose we use a single byte integer stored on the card to indicate the "limit version" the user is currently on. The new limits data for all cardholders will be synchronized to the app with the
transfer_cardendpoint which we already use for things like balance data, and when an out-of-date or unlimited card interacts with an up-to-date version of the app, the new limits will be installed onto that card. The 1 byte integer would allow up to 255 versions, which I think should be enough for most applications given that limits do not change often.Putting it all together!
Now that we've gone through the contents of the card, let's go through a practical example!
The parts we need are
This brings us to 23 bytes used by a card with no limits, so on a 48 byte card we're left with 25 bytes for limits. With limits being either 5 or 7 bytes, we can have between 3 and 5 limits depending on whether they're count-limits or amount-limits.