| 1 | from entity import * |
|---|
| 2 | from container import * |
|---|
| 3 | import occrypto |
|---|
| 4 | import messages |
|---|
| 5 | import coinsplitting |
|---|
| 6 | import sys |
|---|
| 7 | |
|---|
| 8 | class 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 | |
|---|