CS 2360 Assignment 3: Due Midnight Feb 6

Digital Cash

This is assignment is a tribute to David Chaum, the cryptologist who invented digital cash in the 1980's. If you would like more information about digital cash, cryptography, and/or David Chaum, please see me and I can provide you with the the appropriate references. All of my discussion of this material comes from the book Applied Cryptography by Bruce Schnier (available at any decent bookstore!).

This assignment is an exercise in abstraction: You should seek to implement as abstract a solution as possible, because you don't know all the details! If you do your work carefully and abstractly, your solution should be easily modifiable, if presented with a more complex scenario later....

Let us assume that at some point in the not-too-distant future all cash as we know it is eliminated by the government by law. They now make it a requirement that all financial transactions occur electronically over the (just built) information superhighway (or as it known at the time by the populace, "the Information Ho Chi Min trail"). The problem is that now the government can track all purchases made by its citizens via credit (or ATM) cards. This is clearly not a good property, so here you come to the rescue!

You've decided that what the country needs is a form of digital cash. This "digicash," as it is often known, will not violate the law requiring all funds transfers to be electronic but yet will preserve the two good properties of the old form of physical cash:

Why would you still want cash in the brave new world of the Information H.C.M. Trail?

Now given that we've established that there might interest in digital cash (especially among politicians/drug dealers!), you go to your favorite set of venture capitalists claiming that you know how to make this work and that David Chaum figured out all the mathematics back in the 80's! You say, "All I need is 100 million dollars to build a nationwide system to make it work!" The venture capitalists (read: your TA's) aren't stupid enough however to believe such an idea (not to mention set you up with 100 Megabucks) without a reasonable amount of proof. They want a prototype system; naturally, you should prototype it in Lisp!

They tell you that your prototype should abstract away all the details of the cryptographic system (whew!) that would really be necessary to make all this work, and just implement the basics of the protocols necessary for the digicash systems.

A protocol is sequence of message exchanges between the two parties involved in a transaction (in this specific case, it is always a funds transfer transaction). Here is an example of an existing transaction you are all familiar with:

Now, there are some other steps in this protocol that are not shown above, like what does the credit card company do behind the scenes while it is figuring out its answer? What does the credit card company do when it says "yes" or "no"? (Hint: These involves some database work.)

Some more interesting questions come up involving cheating. Some examples are: What happens if you call up the credit card company (or clerk) and claim you didn't really purchase something? What does the company or clerk do to prove you wrong? What do you do if you get a bill at the end of the month that has the wrong amount (or you want to try to falsely claim so)? How does the merchant actually get the money from the credit card company? Most importantly: Who can "cheat" in this protocol and how?

Now, given that we have seen the complexities of this problem for credit cards, how are you going to make all this work for digicash? Luckily, our friend Mr. Chaum has already figured this out a while back. There are several protocols involved in the implementation of digital cash, and I'll spell each of them out individually.

Getting Some Digital Cash

Let's assume you have an account with a major bank, say Citibank. and you want to get some digital cash out. They are willing to give out such cash under exactly one condition: They don't get cheated out of money. (Banks are funny this way.) Given this condition, they really are more than happy to give out digital cash; they don't care what you do with your money, other than cheat them out of theirs.

Here's the physical analogy of the protcol for going to the bank and getting a quick $100 out of your account in digital cash form:

At this point, you are have a $100 D.C. with the Citibank seal of approval on it. Any merchant (or congressman!) would be happy to accept your certificant since Citibank guaranteed it.

The astute reader will have noted that there is a problem here: What stops you from just making some photocopies of the D.C.? It would be a license to print money! The very astute reader will have noted that there is also a solution here: the uniqueness string.

Checking Up On Digital Cash

Ok, say you go to your favorite questionable video store, and want to buy Last Tango In Paris. How does the video store place check to make sure that you are using legitimate digital cash when you get up to the counter with your video? Here's the protocol: Note that this is just a check for validity, it doesn't commit the merchant (or you) to spending your D.C. A person who suspicious of the bank looking through his sealed envelope with some sort of spy device when he gets the D.C. can use this information as well! If you call up the bank claiming to be a merchant and check on a given identity string, you know if they are cheating on you or not! (Well, there are some more complex scenarios where you might still be fooled, but this is not a bad approximation...)

Spending Digital Cash

Ok, now the merchant has checked out the D.C. you have presented, how do we actually spend the money? About how the bank figures out who's cheating: If the uniqueness string has been used but with a different identity string, the purchaser (not the merchant) has been photocopying D.C.'s and should be jailed, but, unfortunately, we will have some trouble finding him, he is using digital cash!. If the same uniqueness string has been used before with the same identity string then its the merchant who is photocopying ("Guards, take him away!") and should be put in the slammer. Keep in mind, that the purchaser shouldn't be able to get away with photocopying if the merchant checks the D.C. for validity before accepting it and take the D.C. to the bank in a timely fashion (like, say, that second!).

Again, the astute reader will note a problem: The success of this system hinges crucially on the inability of any participant to alter D.C.'s after some other participant has made some commitment to it. In a real implementation, cryptography would be used to provide those guarantees, but we are going abstract this issue out of the problem. We are going to implement the higher level solutions and not worry about the details.

Coding This Sucker Up

Overall Directions

Before you can get too far, you'll need a general purpose storage facility. I'll give this too you, since it needsd to perform some black magic in its implementation. Keep in mind that you shouldn't be too concerned about how this is implemented, you should be concerned about this code in the abstract. This code is a general purpose storage facility, called "general-database" and it has six functions (plus a helper) that you'll need: (defun general-database-initialize (name-of-database) (set name-of-database nil)) (defun general-database-add (name-of-database key value) (set name-of-database (cons (cons key (cons value nil)) (eval name-of-database)))) (defun general-database-exists-p (name-of-database) (boundp name-of-database)) (defun general-database-contains-p (name-of-database key) (assoc key name-of-database :test #'equal)) (defun general-database-delete (name-of-database key) (set name-of-database (general-database-delete-helper (eval name-of-database) key nil))) (defun general-database-delete-helper (database target result) (cond ((null database) result) ((equal target (caar database)) (general-database-delete-helper (cdr database) target result)) (t (general-database-delete-helper (cdr database) target (cons (car database) result))))) (defun general-database-change (name-of-database target new-value) (if (general-database-contains-p (eval name-of-database) target) (progn (general-database-delete name-of-database target) (general-database-add name-of-database target new-value)))) You can use this to create databases (giving them a name), add things to them, check for the value of things in the database, make sure databases exist, change things in databases, and delete things from a database. You should be sure you understand how to use these functions before going very far on the assignment. Here is an example of their usage: (general-database-initialize 'some-database) NIL > (general-database-add 'some-database 'some-key 'some-value) ((SOME-KEY SOME-VALUE)) > (general-database-add 'some-database 'my-key 'another-value) ((MY-KEY ANOTHER-VALUE) (SOME-KEY SOME-VALUE)) > (general-database-contains-p some-database 'my-key) (MY-KEY ANOTHER-VALUE) > (general-database-contains-p some-database 'bogus-key) NIL > (general-database-contains-p some-database 'some-key) (SOME-KEY SOME-VALUE) > (general-database-add 'some-database "testing 1 2 3" "emergency broadcast") (("testing 1 2 3" "emergency broadcast") (MY-KEY ANOTHER-VALUE) (SOME-KEY SOME-VALUE)) > (general-database-contains-p some-database "testing 1 2 3" ) ("testing 1 2 3" "emergency broadcast") > (general-database-contains-p some-database "TESTING 1 2 3" ) NIL > (general-database-exists-p 'some-database) T > (general-database-exists-p 'bogus-db) NIL >my-db (("some string" "some value") (LYMAN TAYLOR) (IAN SMITH)) > (general-database-delete 'my-db 'lyman) ((IAN SMITH) ("some string" "some value")) > (general-database-delete 'my-db 'ian) (("some string" "some value")) > (general-database-delete 'my-db "some-value") (("some string" "some value")) > (general-database-delete 'my-db "some string") NIL > (general-database-delete 'my-db 'yessir) NIL > (general-database-add 'my-db 'yessir 'ree) ((YESSIR REE)) > (general-database-add 'my-db 'right 'whatever) ((RIGHT WHATEVER) (YESSIR REE)) > (general-database-delete 'my-db 'right) ((YESSIR REE)) > my-db ((YESSIR REE)) > > (general-database-change 'my-db 'lyman 'foobar) NIL > my-db ((YESSIR REE)) > (general-database-change 'my-db 'yessir 'foobar) ((YESSIR FOOBAR)) > my-db ((YESSIR FOOBAR)) > (general-database-add 'my-db 'why 'not) ((WHY NOT) (YESSIR FOOBAR)) > (general-database-change 'my-db 'why 'ask-why) ((WHY ASK-WHY) (YESSIR FOOBAR)) > (general-database-contains-p my-db 'why) (WHY ASK-WHY) Play with these functions for a while to get a feel for them!

Note that the initialize, add, change, and delete functions require that you put a quote (') in front of the name of the database. Further, these functions do not compute a useful value; they are called for their side effects. The contains-p function does not use a quote in front of the name of the database, and further, its result is quite useful! One final caveat, these functions create a global variable which is the name of the database; this may be useful for debugging. The exists-p predicate is there so you can know when to call initialize! You'll need to do this sometimes to get code to work out right.

Note: If you need to remove all the entries in a database, you can just initialize it again.

Part 1

Create some functions that can do the "bookkeeping" necessary for banks to keep track of accounts. Naturally, you should use the general database function above. You only need to implement one bank, so all the accounts can be in the same database. Here are the functions you should write:
bank-account-create
This function should create an new account with a balance of $1000; it should do nothing if there already exists an account with this name. It should take the name of the account as a parameter. This function does not need to compute a useful value.
bank-account-balance
This function should take an account name as a parameter and return the amount in the account by that name. This function should return nil for accounts that don't exist.
bank-account-credit
This function should take the name of an account and an amount as parameters and credit (add) the amount to the account. If the account name is invalid (no such account) this function should do nothing.
bank-account-debit
This is the same as bank-account-debit except it subtracts from the account. You can assume that we will not call credit and debit on an account before we have created it. If they try to withdraw more money than they have, you should use the function ERROR to signal an error. ERROR takes a string as an argument which should explain the error.

Part 2

You should now implement some functions involving the creation of digital cash certificates. You will not be graded on how you implement the digital cash certificate, only if the functions do their jobs or not. This is an abstraction barrier, i.e. you can assume that we will not look beyond the barrier at the implementation of the data structure.

Here are some functions you should write:

digicash-create-certificate
This function should take an argument of the amount of digital cash to put on the certificate (how much is it for). This function should return a D.C. This certificae should contain both the amount and a random uniqueness string. You may find this function useful for generating random uniqueness strings. (defun create-random-string (of-length) (cond ((= of-length 0) (make-sequence 'string 0)) (t (concatenate 'string (make-sequence 'string 1 :initial-element (elt "abcdefghijklmnopqrstuvwxyz0123456789" (random 36))) (create-random-string (- of-length 1)))))) These strings should be at least 10 characters long in your implementation.
digicash-get-amount
This should return the amount of a digital cash certificate given as its input.
digicash-get-uniqueness-string
This should return the uniqueness string of a digital cash certificate.
digicash-create-envelope-set
This function should take a number of envelopes and an amount as its parameters. This function should return a list of digital cash certificates, each one generated by the digicash-create-certificate function with the parameter of the amount.
digicash-same-certificate-p
This function takes two digital cash certificates and returns T or NIL based on whether or not they are equal. Two certificates are equal if they have the same uniqueness string (that's the point!).

Part 3

Now we are starting to really cook! What you need now is a way for various parties in the transactions to have a way to keep track of uniqueness strings they have seen. Use the general database code to construct a set of functions like this:
uniqueness-database-create
This function should create a new (or re-initialize the old) database for a participant to use to keep track of strings they have seen. The parameter should be the name of the database to create.
uniqueness-database-exists-p
This function should take the name of a uniqueness database and return true or false based on whether or not such a database exists.
uniqueness-database-add
This function should take the name of a database and a uniqueness string and add this string to the database of seen strings. Note that since there is not a "value" for these strings to be paired with (in a proper sense) you'll need to make up something.
uniqueness-database-have-seen-p
Normally, I'd make you write this function, but there is a subtle point lurking here about the binding of names to values. I'd like you to just use this function, but have your comments reflect why this code is necessary: (defun uniqueness-database-have-seen-p (db-name target) (if (general-database-contains-p (eval db-name) target) t nil))

Part 4

Given this code, we can start building the protocols discussed above and in class. Let's try the protocol by which a user gets a a D.C. from the bank, and has it stamped. Here are some functions to write.
digicash-stamp
This function should take in an unstamped digicash certificate and return a stamped one. It should do nothing if it finds the D.C. to be already stamped. You can implement this however you want. It will be useful to modify your digicash-create-certificate to indicate that the D.C.'s are unstamped when they are created.
digicash-is-stamped-p
This function should return T or NIL based on whether or not a digicash certificate is stamped. A freshly created digicash certificate should obviously not be stamped!
digicash-remove-envelope
This function should take an envelope set and a D.C. as parameters. This function should use the REMOVE-IF function (with a LAMBDA expression!) and the digicash-same-certificate-p to return another envelope set which does not have the D.C. passed in as a parameter
digicash-check-envelope-set
This function should take a set of envelopes and an amount as parameters. It should examine each one making sure that each one has the amount that it got as a parameter, and that it hasn't seen the uniqueness string before. (Note: This may entail creation of a database for your bank if it doesn't exist yet.) This function should look at ALL envelopes passed to it. Note: each time it looks at an envelope it should note the uniqueness string in its database. If any envelope is bogus, this function should return NIL, otherwise (assuming everything is ok) it should return T.

Note that as you are testing this, the database is filling up with values, so you'll need to watch out for these side-effects.

digicash-get-stamped
This function should take an envelope set, an amount, and a number as parameters. It should return a stamped D.C. of the amount passed in as the amount, which should be the NTH element of the list of envelopes you generate. Use the third parameter with the NTH function to select it. If you find that your envelope set is bogus, you should return NIL.

You will be much happier with your grade, if you write these using any and all functions you have already written as well as functions in this section.

The following piece of code can be used to get digital cash out of the bank:

>(digicash-get-stamped (digicash-create-envelope-set 500 100) 100 6) (100 "in0os4rrpli6lutgex76xrux0wosx6" T) This creates 500 envelopes of denomination 100 with random strings. It claims that they are all 100 denomination, and that the bank should select the sixth one to stamp. Slightly better is: > (digicash-get-stamped (digicash-create-envelope-set 500 100) 100 (random 500)) (100 "1iwr4hwwsnj9cs319o9sm00q8iy77e" T) which picks one of the envelopes of the 500 randomly.

Note that your returned value may be different from mine if you chose to implement your digicash certificates differently from me.

Part 5

Write a driver function called digicash-test which takes an amount and an account name and returns a stamped D.C. of the appropriate denomination, and deducts the amount from the account name mentioned (this will error if this not legal, so you don't have to check this). This function should return the stamped D.C (or NIL if there is cheating) and you may deduct from the account before doing the envelope protocol if you find this easier. You can hard-code the number of envelopes to use and the algorithm for picking the envelope based on your interpretration of paraniod bankers are...

Use the code listed above to guide you on this function! You can assume we won't try to get a certificate with this function unless the person already has an account with the bank via bank-account-create.

Part 6?

If you are adventurous, it is not very hard to hack up the other protocols based on the explanations above... You might try just seeing if you can get it to work... A successful implementation won't get you any more credit, but might get you the admiration of your instructor, TAs, classmates, and David Chaum! You'll need to (naturally) go back and add some more code that handles the signature of the person using the digital cash, and more code to handle the "turning in" of the certificates at the bank... Enjoy!


Back To The CS2360 Home Page
Ian Smith (iansmith@cc.gatech.edu)

Last Modified 17 Jan 95 by Ian Smith (iansmith@cc.gatech.edu)