root / trunk / sandbox / jhb / oc2 / wallet.py

Revision 340, 12.6 kB (checked in by ocjhb, 6 months ago)

forgotten debug print

  • Property svn:mime-type set to text/plain
  • Property svn:eol-style set to native
  • Property svn:executable set to *
Line 
1from entity import *
2from container import *
3import occrypto
4import messages
5import coinsplitting
6import sys
7
8class Wallet(Entity):
9
10    def _makeBlank(self,cdd,mkc):
11        blank = container.Coin()
12        blank.standardId = cdd.standardId
13        blank.currencyId = cdd.currencyId
14        blank.denomination = mkc.denomination
15        blank.keyId = mkc.keyId
16        blank.setNewSerial()
17        return blank
18
19    def blanksFromCoins(self,coins):
20        pass
21
22    def makeSerial(self):
23        return occrypto.createSerial()
24   
25    def addOutgoing(self,message):
26        self.storage.setdefault('outgoing',{})[message.transactionId] = message
27
28    def getOutgoing(self,tid):
29        return self.storage.setdefault('outgoing',{})[tid]
30
31    def addIncoming(self,message):
32        self.storage.setdefault('incoming',{})[message.transactionId] = message
33        #sys.stderr.write(str(self.storage.filename))
34
35    def getIncoming(self,tid):
36        #sys.stderr.write(str(tid))
37        #sys.stderr.write(str(self.storage.filename))
38        transaction = self.storage.setdefault('incoming',{}).get(tid,None)
39        if transaction != None:
40            del(self.storage['incoming'][tid])
41        return transaction           
42
43       
44
45    def askLatestCDD(self,transport):
46        self.feedback('Talking to issuer: fetching latest CDD')
47        response = transport(messages.AskLatestCDD())
48        if not isinstance(response,messages.GiveLatestCDD):
49            raise Exception, 'Not a valid message'
50        if not response.cdd.masterPubKey.verifyContainerSignature(response.cdd):
51            raise Exception, 'Could not verify cdd'
52        return response.cdd
53
54
55    def fetchMintKeys(self,transport,cdd,denominations=None,keyids=None):
56        if denominations and keyids:
57            raise Exception, "you can't ask for denominations and keyids at the same time"
58        if not (denominations or keyids):
59            raise Exception, "you need to ask at least for one"
60        message = messages.FetchMintKeys()
61        denominations = [str(d) for d in denominations]
62        message.denominations = denominations
63        message.keyids = keyids
64        self.feedback('Talking to issuer: fetching mintkeys')
65        response = transport(message)
66       
67        if response.header == 'MINTING_KEY_FAILURE':
68            raise message
69       
70        if not isinstance(response,messages.GiveMintKeys):
71            raise Exception, 'Not a valid message'
72       
73        if set(denominations).difference(set([key.denomination for key in response.keys])):
74            raise Exception, 'Not all denominations met'
75
76        for key in response.keys:
77            if not cdd.masterPubKey.verifyContainerSignature(key):
78                raise Exception, 'Could not verify key'
79        return  response.keys
80           
81       
82
83    def requestTransfer(self,transport,transactionId,target=None,blinds=None,coins=None):
84        if target and blinds:
85            requesttype = 'mint'
86        elif target and coins:
87            requesttype = 'redeem'
88        elif blinds and coins:
89            requesttype = 'exchange'
90        else:
91            raise 'Not a valid combination of options'
92       
93        message = messages.TransferRequest()
94        message.transactionId = transactionId
95        message.target = target
96        message.blinds = blinds
97        message.coins = coins
98        message.options = dict(type=requesttype).items()
99        self.feedback('Talking to issuer: request %s' % requesttype)
100        response = transport(message)
101        return response
102
103    def resumeTransfer(self,transport,transactionId):
104        message = messages.TransferResume()
105        message.transactionId = transactionId
106        response = transport(message)
107        return response
108
109
110    def announceSum(self,transport,tid,amount,target):
111        message = messages.SumAnnounce()
112        message.transactionId = tid
113        message.amount = amount   
114        message.target = target
115        self.addOutgoing(message)
116        response = transport(message)
117        if response.header == 'SumReject':
118            return response.reason
119        else:
120            return True
121
122    def listenSum(self,message):
123        approval = self.getApproval(message)
124        if approval == True:
125            answer = messages.SumAccept()
126            self.addIncoming(message)
127        else:
128            answer = messages.SumReject()
129            answer.reason = approval
130        answer.transactionId = message.transactionId           
131        return answer
132
133    def requestSpend(self,transport,tid,coins):
134        message = messages.SpendRequest()
135        message.transactionId = tid
136        message.coins = coins
137        response = transport(message)
138        if response.header == 'SpendReject':
139            raise response
140        else:
141            return True
142
143
144    def listenSpend(self,message,transport=None):
145        tid = message.transactionId
146        amount = sum([int(m.denomination) for m in message.coins])
147        #check transactionid
148        orig = self.getIncoming(tid)
149        if not orig:
150            answer = messages.SpendReject()
151            answer.reason = 'unknown transactionId'
152            return answer
153
154        #check sum
155        if amount != int(orig.amount):
156            answer = messages.SpendReject()
157            answer.reason = 'amount of coins does not match announced one. Announced: %s, got %s' % (orig.amount, amount)
158            return answer
159
160        #do exchange
161        if transport:
162            cdd = self.askLatestCDD(transport)
163            currency = self.getCurrency(cdd.currencyId)
164            newcoins = message.coins
165            answer = self.freshenUp(transport,cdd,newcoins)
166            if answer.header != 'TransferAccept':
167                answer = messages.SpendReject()
168                answer.reason = 'did not go through'
169                return answer
170       
171        answer = messages.SpendAccept()
172        answer.transactionId = tid
173        return answer
174
175
176    def getCurrency(self,id):
177        if self.storage.has_key(id):
178            return self.storage[id]
179        else:
180            currency = dict(cdds=[],
181                            blanks = {},
182                            coins = [],
183                            transactions = {})
184            self.storage[id]=currency
185            return currency
186
187    def listCurrencies(self):
188        out = []
189        for key,currency in self.storage.items():
190            try:
191                cdd = currency['cdds'][-1]
192                amount = sum([int(coin.denomination) for coin in currency['coins']])
193                out.append((cdd,amount))
194            except:
195                #del(self.storage[key]) XXX why was that?
196                pass
197        return out           
198
199    def deleteCurrency(self,id):
200        del(self.storage[id])
201   
202    def tokenizeForBuying(self,amount,denominations):
203        return coinsplitting.tokenizer([int(d) for d in denominations],amount)                   
204
205    def pickForSpending(self,amount,coins):
206        tmp = [(int(c.denomination),c) for c in coins]
207        tmp.sort()
208        tmp.reverse()
209        mycoins = [t[1] for t in tmp]
210        picked = []
211        for coin in mycoins:
212            sumpicked = sum([int(c.denomination) for c in picked])
213            if sumpicked < amount:
214                if int(coin.denomination) <= (amount - sumpicked):
215                    picked.append(coin)
216            else:
217                break
218        return picked               
219
220    def getApproval(self,message):
221        amount = message.amount
222        target = message.target
223        approval = getattr(self,'approval',True) #get that from ui
224        return approval
225
226    def feedback(self,message):
227        #print message
228        pass
229
230#################################higher level#############################
231
232    def addCurrency(self,transport):
233        cdd = self.askLatestCDD(transport)
234        id = cdd.currencyId
235        currency = self.getCurrency(id)
236        if cdd.version not in [cdd.version for cdd in currency['cdds']]:
237            currency['cdds'].append(cdd)
238
239    def mintCoins(self,transport,amount,target):
240        cdd = self.askLatestCDD(transport)
241        currency = self.getCurrency(cdd.currencyId)
242        tokenized =  self.tokenizeForBuying(amount,cdd.denominations) #what coins do we need
243        tid = self.makeSerial()
244        secrets,data = self.prepareBlanks(transport,cdd,tokenized)
245        response = self.requestTransfer(transport,tid,target,data,[])
246        signatures = response.signatures
247        currency['coins'].extend(self.unblindWithSignatures(secrets,signatures))
248        self.storage.save()
249
250    def prepareBlanks(self,transport,cdd,values):       
251        wanted = list(set(values)) #what mkcs do we want
252        keys = self.fetchMintKeys(transport,cdd,denominations=wanted)
253        mkcs = {}
254        for mkc in keys:
255            if not cdd.masterPubKey.verifyContainerSignature(mkc):
256                raise Exception, 'Could not verify mkc'
257            mkcs[mkc.denomination] = mkc
258       
259        secrets = []
260        data = []
261        self.feedback('Talking to issuer: preparing blanks')
262        for denomination in values:
263            mkc = mkcs[str(denomination)]
264            blank = self._makeBlank(cdd,mkc)
265            secret,blind = mkc.publicKey.blindBlank(blank)
266            secrets.append((blank,blind,mkc,secret))
267            data.append((mkc.keyId,blind))
268        return secrets,data
269
270
271       
272    def unblindWithSignatures(self,secrets,signatures):       
273        i = 0
274        coins = []
275        for signature in signatures:
276            blank,blind,mkc,secret = secrets[i]
277            key = mkc.publicKey
278            blank.signature = key.unblind(secret,signature)
279            coin = blank
280            if not key.verifyContainerSignature(coin):
281                raise 'Invalid signature'
282            coins.append(coin)
283            i += 1
284        return coins           
285
286    def getAllCoins(self,currencyId):
287        currency = self.getCurrency(currencyId)
288        tmp = [(int(c.denomination),c) for c in currency['coins']]
289        tmp.sort()
290        return [t[1] for t in tmp]
291
292
293
294    def redeemCoins(self,transport,amount,target):
295        cdd = self.askLatestCDD(transport)
296        currency = self.getCurrency(cdd.currencyId)
297        coins = currency['coins']
298        picked = self.pickForSpending(amount,coins)
299        tid = self.makeSerial()
300        response = self.requestTransfer(transport,tid,target,[],picked)
301        newcoins = [c for c in coins if c not in picked]
302        currency['coins'] = newcoins       
303        self.storage.save()
304        self.freshenUp(transport,cdd)
305
306
307    def freshenUp(self,transport,cdd,newcoins=[]):       
308        currency = self.getCurrency(cdd.currencyId)
309        paycoins,secrets,data = self.prepare4exchange(transport,cdd,currency['coins'],newcoins)
310        if secrets:
311            tid = self.makeSerial()
312            response = self.requestTransfer(transport,tid,None,data,paycoins+newcoins)
313            if response.header != 'TransferAccept':
314                return response
315            coins = currency['coins']
316            for coin in paycoins:
317                coins.pop(coins.index(coin))
318            coins.extend(self.unblindWithSignatures(secrets,response.signatures))
319            self.storage.save()
320            return response
321        else:
322            return messages.Error()
323
324    def prepare4exchange(self,transport,cdd,oldcoins,newcoins):
325        oldcoins = [c for c in oldcoins]
326        newcoins = [c for c in newcoins]
327       
328        oldvalues = [int(c.denomination) for c in oldcoins]
329        newvalues = [int(c.denomination) for c in newcoins]
330        denominations = [int(d) for d in cdd.denominations]
331        keep,pay,blank = coinsplitting.prepare_for_exchange(denominations,oldvalues,newvalues)
332       
333        if blank:
334            paycoins = []
335            for value in pay:
336                for coin in oldcoins:
337                    if int(coin.denomination) == value:
338                        paycoins.append(oldcoins.pop(oldcoins.index(coin)))
339                        break
340       
341            secrets,data = self.prepareBlanks(transport,cdd,blank)
342            return paycoins,secrets,data
343        else:
344            return [],[],[]
345
346
347    def spendCoins(self,transport,currencyId,amount,target):
348        currency = self.getCurrency(currencyId)
349        coins = currency['coins']
350        picked = self.pickForSpending(amount,coins)
351        tid = self.makeSerial()
352       
353        self.feedback(u'Spending coins: wating for confirmation')
354        response = self.announceSum(transport,tid,amount,target)
355        self.feedback(u'Spending coins: wating for other side')
356        response = self.requestSpend(transport,tid,picked)
357        if response == True:
358            newcoins = [c for c in coins if c not in picked]
359            currency['coins'] = newcoins       
360            self.storage.save()
361
Note: See TracBrowser for help on using the browser.