| 1 | import base64 |
|---|
| 2 | import json |
|---|
| 3 | import types |
|---|
| 4 | |
|---|
| 5 | class Container(object): |
|---|
| 6 | r"""A generic container, handles serializing |
|---|
| 7 | |
|---|
| 8 | >>> c = Container(foo='foo',bar='bar') |
|---|
| 9 | |
|---|
| 10 | Accessing c.foo should not work, as the container has no |
|---|
| 11 | infos about its fields |
|---|
| 12 | |
|---|
| 13 | >>> c.foo |
|---|
| 14 | Traceback (most recent call last): |
|---|
| 15 | ... |
|---|
| 16 | AttributeError: 'Container' object has no attribute 'foo' |
|---|
| 17 | |
|---|
| 18 | Lets try again: |
|---|
| 19 | >>> Container.fields = ['foo','bar'] |
|---|
| 20 | >>> c = Container(foo='foo',bar='bar') |
|---|
| 21 | >>> c |
|---|
| 22 | <Container(foo='foo',bar='bar')> |
|---|
| 23 | |
|---|
| 24 | Serialize human readable - its actually json. |
|---|
| 25 | >>> c.content_part() |
|---|
| 26 | '[["foo","foo"],["bar","bar"]]' |
|---|
| 27 | |
|---|
| 28 | Serialize to json |
|---|
| 29 | >>> j = c.toJson() |
|---|
| 30 | >>> j |
|---|
| 31 | '[["foo","foo"],["bar","bar"]]' |
|---|
| 32 | |
|---|
| 33 | Lets deserialize |
|---|
| 34 | >>> c = Container() |
|---|
| 35 | >>> c |
|---|
| 36 | <Container(foo=None,bar=None)> |
|---|
| 37 | |
|---|
| 38 | >>> c.fromJson(j) |
|---|
| 39 | <Container(foo='foo',bar='bar')> |
|---|
| 40 | |
|---|
| 41 | >>> c |
|---|
| 42 | <Container(foo='foo',bar='bar')> |
|---|
| 43 | |
|---|
| 44 | Add a codec for bar |
|---|
| 45 | >>> c.codecs['bar'] = {'encode':base64.encodestring,'decode':base64.decodestring} |
|---|
| 46 | |
|---|
| 47 | Lets look at the json now |
|---|
| 48 | >>> j2 = c.toJson() |
|---|
| 49 | >>> j2 |
|---|
| 50 | '[["foo","foo"],["bar","YmFy\\n"]]' |
|---|
| 51 | |
|---|
| 52 | >>> c.fromJson(j2) |
|---|
| 53 | <Container(foo='foo',bar='bar')> |
|---|
| 54 | """ |
|---|
| 55 | |
|---|
| 56 | fields = [] |
|---|
| 57 | codecs = {} |
|---|
| 58 | content_id = 'Container' |
|---|
| 59 | |
|---|
| 60 | def __init__(self,**kwargs): |
|---|
| 61 | """This would set up the data""" |
|---|
| 62 | |
|---|
| 63 | for field in self.fields: |
|---|
| 64 | setattr(self,field,kwargs.get(field,None)) |
|---|
| 65 | self.content_id = self.__class__.__name__ |
|---|
| 66 | |
|---|
| 67 | def __repr__(self): |
|---|
| 68 | arguments = ','.join(["%s=%s" %(field,repr(getattr(self,field))) for field in self.fields]) |
|---|
| 69 | return "<%s(%s)>" % (self.__class__.__name__,arguments) |
|---|
| 70 | |
|---|
| 71 | def __str__(self): |
|---|
| 72 | return self.toJson() |
|---|
| 73 | |
|---|
| 74 | def encodeField(self,fieldname): |
|---|
| 75 | '''returns the value of field in whatever string represnation''' |
|---|
| 76 | |
|---|
| 77 | encoder = self.codecs.get(fieldname,{}).get('encode',lambda x: x) |
|---|
| 78 | return encoder(getattr(self,fieldname)) |
|---|
| 79 | |
|---|
| 80 | def decodeField(self,fieldname,text): |
|---|
| 81 | '''returns the value of field in whatever string represnation''' |
|---|
| 82 | |
|---|
| 83 | decoder = self.codecs.get(fieldname,{}).get('decode',lambda x: x) |
|---|
| 84 | return decoder(text) |
|---|
| 85 | |
|---|
| 86 | def setCodec(self,fieldname,encoder=None,decoder=None): |
|---|
| 87 | |
|---|
| 88 | donothing = lambda x: x |
|---|
| 89 | if not encoder: |
|---|
| 90 | encoder = donothing |
|---|
| 91 | if not decoder: |
|---|
| 92 | decoder = donothing |
|---|
| 93 | |
|---|
| 94 | self.codecs[fieldname] = {'encode':encoder,'decode':decoder} |
|---|
| 95 | |
|---|
| 96 | def toPython(self, extranames=False): |
|---|
| 97 | return [(fieldname,self.encodeField(fieldname)) for fieldname in self.fields] |
|---|
| 98 | |
|---|
| 99 | def fromPython(self,data): |
|---|
| 100 | i = 0 |
|---|
| 101 | for fieldname in self.fields: |
|---|
| 102 | setattr(self,fieldname,self.decodeField(fieldname,data[i][1])) |
|---|
| 103 | i += 1 |
|---|
| 104 | return self |
|---|
| 105 | |
|---|
| 106 | def content_part(self): |
|---|
| 107 | '''returns a human readable representation of the content''' |
|---|
| 108 | |
|---|
| 109 | return self.toJson(False) |
|---|
| 110 | |
|---|
| 111 | content = ';'.join(['"%s"="%s"' % t for t in self.toPython()]) |
|---|
| 112 | return "%s={%s}" % (self.content_id,content) |
|---|
| 113 | |
|---|
| 114 | def toJson(self, extranames=False): |
|---|
| 115 | return json.write(self.toPython(extranames)) |
|---|
| 116 | |
|---|
| 117 | def fromJson(self,text): |
|---|
| 118 | return self.fromPython(json.read(text)) |
|---|
| 119 | |
|---|
| 120 | def __eq__(self,other): |
|---|
| 121 | #return self.__dict__== other.__dict__ |
|---|
| 122 | if hasattr(self, 'content_part') and hasattr(other, 'content_part'): |
|---|
| 123 | return self.content_part() == other.content_part() |
|---|
| 124 | else: |
|---|
| 125 | return False |
|---|
| 126 | |
|---|
| 127 | |
|---|
| 128 | def serialize(self): |
|---|
| 129 | import pickle |
|---|
| 130 | import base64 |
|---|
| 131 | |
|---|
| 132 | return base64.b64encode(pickle.dumps(self)) |
|---|
| 133 | |
|---|
| 134 | |
|---|
| 135 | def encodeTime(seconds): |
|---|
| 136 | #FIXME: this breaks if we are greater than whatever the epoc is (usually 2038) |
|---|
| 137 | import time |
|---|
| 138 | # we have to be careful here. Each field must be atleast 2 characters long. |
|---|
| 139 | instant = ['%02d' % k for k in time.gmtime(seconds)[:6]] |
|---|
| 140 | return '-'.join(instant[:3]) + 'T' + ':'.join(instant[3:6]) + 'Z' |
|---|
| 141 | |
|---|
| 142 | def decodeTime(s): |
|---|
| 143 | """decodes a time |
|---|
| 144 | |
|---|
| 145 | >>> decodeTime('2008-02-01T00:00:00Z') |
|---|
| 146 | 1201824000 |
|---|
| 147 | |
|---|
| 148 | >>> decodeTime('foo') |
|---|
| 149 | Traceback (most recent call last): |
|---|
| 150 | TypeError: ... |
|---|
| 151 | |
|---|
| 152 | """ |
|---|
| 153 | #FIXME: this breaks if we are greater than whatever the epoc is (usually 2038) |
|---|
| 154 | import time, calendar |
|---|
| 155 | try: |
|---|
| 156 | date, t = s.split('T') |
|---|
| 157 | year, month, day = date.split('-') |
|---|
| 158 | if len(year) < 4 or len(month) != 2 or len(day) != 2: |
|---|
| 159 | raise TypeError('Encoding of date "%s" is incorrect' % s) |
|---|
| 160 | if t[-1] != 'Z': |
|---|
| 161 | raise TypeError('Encoding of date "%s" is incorrect' % s) |
|---|
| 162 | t = t[:-1] # get rid of the 'Z' |
|---|
| 163 | hour, min, sec = t.split(':') |
|---|
| 164 | # Okay. If all these splits worked, we know that we have correctly formatted string. |
|---|
| 165 | # so let strptime deal with anything odd |
|---|
| 166 | except ValueError: |
|---|
| 167 | raise TypeError('Encoding of date "%s" is incorrect' % s) |
|---|
| 168 | |
|---|
| 169 | struct = time.strptime(s, '%Y-%m-%dT%H:%M:%SZ') |
|---|
| 170 | return calendar.timegm(struct) |
|---|
| 171 | |
|---|
| 172 | def validateIntString(s): |
|---|
| 173 | """Returns a string if s is a an encoding of the integer value of s with no spaces or leading/trailing zeros or decimal points.""" |
|---|
| 174 | if str(int(s)) == s: |
|---|
| 175 | return s |
|---|
| 176 | else: |
|---|
| 177 | raise TypeError('Encoding of int "%s" is incorrect' % s) |
|---|
| 178 | |
|---|
| 179 | def validateIntStringList(l): |
|---|
| 180 | """Returns a list of strings if each element in the list passes validateIntString.""" |
|---|
| 181 | for s in l: |
|---|
| 182 | validateIntString(s) |
|---|
| 183 | |
|---|
| 184 | return l |
|---|
| 185 | |
|---|
| 186 | def validateOptionsList(l): |
|---|
| 187 | """Validates an options list is a list of (key, val) pairs of strings.""" |
|---|
| 188 | import types |
|---|
| 189 | if not isinstance(l, types.ListType): |
|---|
| 190 | raise TypeError('Not a valid options list') |
|---|
| 191 | d = {} |
|---|
| 192 | try: |
|---|
| 193 | for key, val in l: |
|---|
| 194 | if key in d: |
|---|
| 195 | raise TypeError('Not a valid options list') |
|---|
| 196 | if not isinstance(key, types.StringType): |
|---|
| 197 | raise TypeError('Not a valid options list') |
|---|
| 198 | if not isinstance(val, types.StringType): |
|---|
| 199 | raise TypeError('Not a valid options list') |
|---|
| 200 | d[key] = val |
|---|
| 201 | except ValueError: |
|---|
| 202 | raise TypeError('Not a valid options list') |
|---|
| 203 | |
|---|
| 204 | if not 'version' in d: |
|---|
| 205 | raise TypeError('Not a valid options list') |
|---|
| 206 | |
|---|
| 207 | return l |
|---|
| 208 | |
|---|
| 209 | |
|---|
| 210 | class Signature(Container): |
|---|
| 211 | """The signature container (a combination of the keyprint and signature fields. |
|---|
| 212 | |
|---|
| 213 | >>> s = Signature(keyprint='0',signature='*') |
|---|
| 214 | >>> s.toJson() |
|---|
| 215 | '[["keyprint","MA=="],["signature","Kg=="]]' |
|---|
| 216 | >>> s == Signature().fromJson(s.toJson()) |
|---|
| 217 | True |
|---|
| 218 | """ |
|---|
| 219 | fields = ['keyprint', |
|---|
| 220 | 'signature'] |
|---|
| 221 | |
|---|
| 222 | codecs = {'keyprint':{'encode':base64.b64encode, 'decode':base64.b64decode}, |
|---|
| 223 | 'signature':{'encode':base64.b64encode, 'decode':base64.b64decode}} |
|---|
| 224 | |
|---|
| 225 | |
|---|
| 226 | class ContainerWithSignature(Container): |
|---|
| 227 | """A container with an optional signature field. |
|---|
| 228 | |
|---|
| 229 | To activate the Json-ing of the signature, supply an argument which is true to |
|---|
| 230 | the function. |
|---|
| 231 | |
|---|
| 232 | >>> class TestContainer(ContainerWithSignature): |
|---|
| 233 | ... fields = ['string', 'number'] |
|---|
| 234 | ... codecs = {'number':{'encode':base64.b64encode, 'decode':base64.b64decode}} |
|---|
| 235 | |
|---|
| 236 | >>> signature = Signature(keyprint='0', signature='*') |
|---|
| 237 | |
|---|
| 238 | >>> test1 = TestContainer(string='hello', number='@') |
|---|
| 239 | >>> test1_j = test1.content_part() |
|---|
| 240 | >>> test1_j |
|---|
| 241 | '[["string","hello"],["number","QA=="]]' |
|---|
| 242 | |
|---|
| 243 | >>> test1.toPython(False) |
|---|
| 244 | [('string', 'hello'), ('number', 'QA==')] |
|---|
| 245 | |
|---|
| 246 | >>> test2 = TestContainer().fromPython(test1.toPython(False)) |
|---|
| 247 | >>> test2 == test1 |
|---|
| 248 | True |
|---|
| 249 | |
|---|
| 250 | >>> test3 = TestContainer().fromJson(test1_j) |
|---|
| 251 | >>> test3 == test1 |
|---|
| 252 | True |
|---|
| 253 | |
|---|
| 254 | Check to make sure toJson fails if we force it to use the signature and we don't |
|---|
| 255 | have it |
|---|
| 256 | >>> test4_j = test1.toJson() |
|---|
| 257 | Traceback (most recent call last): |
|---|
| 258 | ... |
|---|
| 259 | AttributeError: 'NoneType' object has no attribute 'toPython' |
|---|
| 260 | |
|---|
| 261 | >>> test1.content_part() == test1_j |
|---|
| 262 | True |
|---|
| 263 | |
|---|
| 264 | >>> test5 = TestContainer(string='hello', number='@', signature=signature) |
|---|
| 265 | >>> test5_j = test5.toJson() |
|---|
| 266 | >>> test5_j |
|---|
| 267 | '[["string","hello"],["number","QA=="],["signature",[["keyprint","MA=="],["signature","Kg=="]]]]' |
|---|
| 268 | |
|---|
| 269 | >>> test5.toPython() |
|---|
| 270 | [('string', 'hello'), ('number', 'QA=='), ['signature', [('keyprint', 'MA=='), ('signature', 'Kg==')]]] |
|---|
| 271 | >>> TestContainer().fromPython(test5.toPython()).toPython() |
|---|
| 272 | [('string', 'hello'), ('number', 'QA=='), ['signature', [('keyprint', 'MA=='), ('signature', 'Kg==')]]] |
|---|
| 273 | |
|---|
| 274 | >>> test6 = TestContainer().fromJson(test1.content_part()) |
|---|
| 275 | >>> test6.signature = signature |
|---|
| 276 | >>> test6 == test5 |
|---|
| 277 | True |
|---|
| 278 | |
|---|
| 279 | >>> test5.content_part() == test1_j |
|---|
| 280 | True |
|---|
| 281 | |
|---|
| 282 | TODO: Add verify_signature checking |
|---|
| 283 | """ |
|---|
| 284 | def __init__(self, **kwargs): |
|---|
| 285 | Container.__init__(self,**kwargs) |
|---|
| 286 | self.jsontext = None |
|---|
| 287 | self.signature = kwargs.get('signature') |
|---|
| 288 | |
|---|
| 289 | |
|---|
| 290 | def toPython(self, extranames=True): |
|---|
| 291 | if not extranames: |
|---|
| 292 | return [(fieldname,self.encodeField(fieldname)) for fieldname in self.fields] |
|---|
| 293 | else: |
|---|
| 294 | fields = [(fieldname, self.encodeField(fieldname)) for fieldname in self.fields] |
|---|
| 295 | fields.append(['signature', self.signature.toPython()]) |
|---|
| 296 | return fields |
|---|
| 297 | |
|---|
| 298 | def fromPython(self, data): |
|---|
| 299 | i = 0 |
|---|
| 300 | for fieldname in self.fields: |
|---|
| 301 | setattr(self,fieldname,self.decodeField(fieldname,data[i][1])) |
|---|
| 302 | i += 1 |
|---|
| 303 | |
|---|
| 304 | if len(data) - 1 == len(self.fields) and data[i][0] == 'signature': |
|---|
| 305 | s = Signature() |
|---|
| 306 | self.signature = s.fromPython(data[i][1]) |
|---|
| 307 | |
|---|
| 308 | return self |
|---|
| 309 | |
|---|
| 310 | def toJson(self, extranames=True): |
|---|
| 311 | return json.write(self.toPython(extranames)) |
|---|
| 312 | |
|---|
| 313 | def fromJson(self,text): |
|---|
| 314 | return self.fromPython(json.read(text)) |
|---|
| 315 | |
|---|
| 316 | def verifySignature(self, signature_algorithm, hashing_algorithm, key): |
|---|
| 317 | |
|---|
| 318 | content_part = self.content_part() |
|---|
| 319 | hasher = hashing_algorithm(content_part) |
|---|
| 320 | signer = signature_algorithm(key) |
|---|
| 321 | |
|---|
| 322 | if hashing_algorithm(str(key)).digest() != self.signature.keyprint: |
|---|
| 323 | return False |
|---|
| 324 | |
|---|
| 325 | return signer.verify(hasher.digest(), self.signature.signature) |
|---|
| 326 | |
|---|
| 327 | ############################ CDD #################################### |
|---|
| 328 | |
|---|
| 329 | class CurrencyDescriptionDocument(ContainerWithSignature): |
|---|
| 330 | """The CurrencyDescriptionDocument container |
|---|
| 331 | |
|---|
| 332 | Lets test a bit |
|---|
| 333 | >>> import crypto |
|---|
| 334 | >>> ics = crypto.CryptoContainer(signing=crypto.RSASigningAlgorithm, |
|---|
| 335 | ... blinding=crypto.RSABlindingAlgorithm, |
|---|
| 336 | ... hashing=crypto.SHA256HashingAlgorithm) |
|---|
| 337 | |
|---|
| 338 | >>> cdd = CDD(standard_identifier = 'http://opencoin.org/OpenCoinProtocol/1.0', |
|---|
| 339 | ... currency_identifier = 'http://opencent.net/OpenCent', |
|---|
| 340 | ... short_currency_identifier = 'OC', |
|---|
| 341 | ... issuer_service_location = 'opencoin://issuer.opencent.net:8002', |
|---|
| 342 | ... denominations = ['1', '2', '5', '10', '20', '50', '100', '200', '500', '1000'], |
|---|
| 343 | ... issuer_cipher_suite = ics, |
|---|
| 344 | ... options = [['version', '0']], |
|---|
| 345 | ... issuer_public_master_key = crypto.RSAKeyPair(e=17L,n=3233L)) |
|---|
| 346 | |
|---|
| 347 | >>> j = cdd.content_part() |
|---|
| 348 | >>> j |
|---|
| 349 | '[["standard_identifier","http://opencoin.org/OpenCoinProtocol/1.0"],["currency_identifier","http://opencent.net/OpenCent"],["short_currency_identifier","OC"],["issuer_service_location","opencoin://issuer.opencent.net:8002"],["denominations",["1","2","5","10","20","50","100","200","500","1000"]],["issuer_cipher_suite",["RSASigningAlgorithm","RSABlindingAlgorithm","SHA256HashingAlgorithm"]],["options",[["version","0"]]],["issuer_public_master_key","DKE=,EQ=="]]' |
|---|
| 350 | |
|---|
| 351 | >>> cdd2 = CDD().fromJson(j) |
|---|
| 352 | >>> cdd2 == cdd |
|---|
| 353 | True |
|---|
| 354 | |
|---|
| 355 | >>> sig = Signature(keyprint=']', signature='V') |
|---|
| 356 | >>> cdd2.signature = sig |
|---|
| 357 | |
|---|
| 358 | >>> cdd2.toJson() |
|---|
| 359 | '[["standard_identifier","http://opencoin.org/OpenCoinProtocol/1.0"],["currency_identifier","http://opencent.net/OpenCent"],["short_currency_identifier","OC"],["issuer_service_location","opencoin://issuer.opencent.net:8002"],["denominations",["1","2","5","10","20","50","100","200","500","1000"]],["issuer_cipher_suite",["RSASigningAlgorithm","RSABlindingAlgorithm","SHA256HashingAlgorithm"]],["options",[["version","0"]]],["issuer_public_master_key","DKE=,EQ=="],["signature",[["keyprint","XQ=="],["signature","Vg=="]]]]' |
|---|
| 360 | |
|---|
| 361 | |
|---|
| 362 | >>> from tests import CDD as test_cdd |
|---|
| 363 | |
|---|
| 364 | >>> test_j = test_cdd.toJson() |
|---|
| 365 | |
|---|
| 366 | >>> test_cdd2 = CDD().fromJson(test_j) |
|---|
| 367 | >>> test_cdd2 == test_cdd |
|---|
| 368 | True |
|---|
| 369 | |
|---|
| 370 | >>> test_cdd2.signature == test_cdd.signature |
|---|
| 371 | True |
|---|
| 372 | |
|---|
| 373 | >>> test_cdd3 = CDD().fromPython(test_cdd.toPython()) |
|---|
| 374 | >>> test_cdd3 == test_cdd |
|---|
| 375 | True |
|---|
| 376 | |
|---|
| 377 | >>> test_cdd.verify_self() |
|---|
| 378 | True |
|---|
| 379 | """ |
|---|
| 380 | |
|---|
| 381 | from crypto import encodeCryptoContainer, decodeCryptoContainer, decodeRSAKeyPair |
|---|
| 382 | |
|---|
| 383 | fields = ['standard_identifier', |
|---|
| 384 | 'currency_identifier', |
|---|
| 385 | 'short_currency_identifier', |
|---|
| 386 | 'issuer_service_location', |
|---|
| 387 | 'denominations', |
|---|
| 388 | 'issuer_cipher_suite', |
|---|
| 389 | 'options', |
|---|
| 390 | 'issuer_public_master_key'] |
|---|
| 391 | |
|---|
| 392 | codecs = {'issuer_cipher_suite':{'encode':encodeCryptoContainer,'decode':decodeCryptoContainer}, |
|---|
| 393 | 'issuer_public_master_key':{'encode':str, 'decode':decodeRSAKeyPair}, |
|---|
| 394 | 'denominations':{'encode':list, 'decode':validateIntStringList}, |
|---|
| 395 | 'options':{'encode':list, 'decode':validateOptionsList}} |
|---|
| 396 | |
|---|
| 397 | |
|---|
| 398 | def __init__(self,**kwargs): |
|---|
| 399 | ContainerWithSignature.__init__(self, **kwargs) |
|---|
| 400 | self.keytype = kwargs.get('keytype', None) |
|---|
| 401 | |
|---|
| 402 | def verify_self(self): |
|---|
| 403 | """Verifies the self-signed certificate.""" |
|---|
| 404 | import crypto |
|---|
| 405 | |
|---|
| 406 | if 'version' not in dict(self.options): |
|---|
| 407 | return False # required option |
|---|
| 408 | ics = self.issuer_cipher_suite |
|---|
| 409 | return self.verifySignature(ics.signing, |
|---|
| 410 | ics.hashing, |
|---|
| 411 | self.issuer_public_master_key) |
|---|
| 412 | |
|---|
| 413 | |
|---|
| 414 | CDD = CurrencyDescriptionDocument |
|---|
| 415 | |
|---|
| 416 | |
|---|
| 417 | class MintKey(ContainerWithSignature): |
|---|
| 418 | """The MintKey container. |
|---|
| 419 | |
|---|
| 420 | The MintKey container holds everything (almost?) everything required to verify a |
|---|
| 421 | Token. |
|---|
| 422 | |
|---|
| 423 | TODO: Some test go here. Things to test |
|---|
| 424 | |
|---|
| 425 | >>> from calendar import timegm |
|---|
| 426 | >>> from tests import CDD, CDD_private, addSignature |
|---|
| 427 | >>> import crypto, copy |
|---|
| 428 | >>> private, public = crypto.createRSAKeyPair(512) |
|---|
| 429 | >>> key_id = public.key_id(CDD.issuer_cipher_suite.hashing) |
|---|
| 430 | |
|---|
| 431 | >>> mintKey = MintKey(key_identifier=key_id, |
|---|
| 432 | ... currency_identifier='http://opencent.net/OpenCent', |
|---|
| 433 | ... denomination="1", |
|---|
| 434 | ... not_before=timegm((2008,1,1,0,0,0)), |
|---|
| 435 | ... key_not_after=timegm((2008,2,1,0,0,0)), |
|---|
| 436 | ... token_not_after=timegm((2008,4,1,0,0,0)), |
|---|
| 437 | ... public_key=public) |
|---|
| 438 | |
|---|
| 439 | >>> hash_alg = CDD.issuer_cipher_suite.hashing |
|---|
| 440 | >>> sign_alg = CDD.issuer_cipher_suite.signing |
|---|
| 441 | |
|---|
| 442 | >>> def addSignatureAndVerify(mintKey, CDD, signing_key): |
|---|
| 443 | ... ics = CDD.issuer_cipher_suite |
|---|
| 444 | ... mintKey = addSignature(mintKey, ics.hashing, ics.signing, |
|---|
| 445 | ... signing_key, mintKey.key_identifier) |
|---|
| 446 | ... return mintKey.verify_with_CDD(CDD) |
|---|
| 447 | |
|---|
| 448 | >>> mintKey = addSignature(mintKey, hash_alg, sign_alg, CDD_private, CDD.signature.keyprint) |
|---|
| 449 | |
|---|
| 450 | >>> mintKey.verify_with_CDD(CDD) |
|---|
| 451 | True |
|---|
| 452 | |
|---|
| 453 | >>> mintKey.toJson() |
|---|
| 454 | '[["key_identifier","..."],["currency_identifier","http://opencent.net/OpenCent"],["denomination","1"],["not_before","2008-01-01T00:00:00Z"],["key_not_after","2008-02-01T00:00:00Z"],["token_not_after","2008-04-01T00:00:00Z"],["public_key","..."],["signature",[["keyprint","hxz5pRwS+RFp88qQliXYm3R5uNighktwxqEh4RMOuuk="],["signature","..."]]]]' |
|---|
| 455 | |
|---|
| 456 | Well, we are going to test that verify_with_CDD works now. We've already |
|---|
| 457 | built helper a helper function, addSignatureAndVerify. The reason |
|---|
| 458 | we have it is because if we modify the fields, the signature checking will |
|---|
| 459 | fail. So we have to have an incorrect field and a correct signature, to make |
|---|
| 460 | sure we are testing only one error condition at a time. |
|---|
| 461 | >>> mintKey2 = copy.deepcopy(mintKey) |
|---|
| 462 | >>> mintKey2.signature.signature = "foo" |
|---|
| 463 | >>> mintKey2.verify_with_CDD(CDD) |
|---|
| 464 | False |
|---|
| 465 | |
|---|
| 466 | >>> mintKey3 = copy.deepcopy(mintKey) |
|---|
| 467 | >>> mintKey3.signature.keyprint = "foo" |
|---|
| 468 | >>> mintKey3.verify_with_CDD(CDD) |
|---|
| 469 | False |
|---|
| 470 | |
|---|
| 471 | >>> mintKey4 = copy.deepcopy(mintKey) |
|---|
| 472 | >>> mintKey4.key_identifier = "foo" |
|---|
| 473 | >>> addSignatureAndVerify(mintKey2, CDD, CDD_private) |
|---|
| 474 | False |
|---|
| 475 | |
|---|
| 476 | >>> mintKey5 = copy.deepcopy(mintKey) |
|---|
| 477 | >>> mintKey5.public_key = "foo" |
|---|
| 478 | >>> addSignatureAndVerify(mintKey2, CDD, CDD_private) |
|---|
| 479 | False |
|---|
| 480 | |
|---|
| 481 | >>> mintKey6 = copy.deepcopy(mintKey) |
|---|
| 482 | >>> mintKey6.currency_identifier = "foo" |
|---|
| 483 | >>> addSignatureAndVerify(mintKey6, CDD, CDD_private) |
|---|
| 484 | False |
|---|
| 485 | |
|---|
| 486 | >>> mintKey7 = copy.deepcopy(mintKey) |
|---|
| 487 | >>> mintKey7.denomination = "1.4" |
|---|
| 488 | >>> addSignatureAndVerify(mintKey7, CDD, CDD_private) |
|---|
| 489 | False |
|---|
| 490 | |
|---|
| 491 | >>> mintKey8 = copy.deepcopy(mintKey) |
|---|
| 492 | >>> mintKey8.signature = None |
|---|
| 493 | >>> mintKey8.verify_with_CDD(CDD) |
|---|
| 494 | False |
|---|
| 495 | |
|---|
| 496 | Just to make sure we didn't mess up something on the way... |
|---|
| 497 | >>> mintKey.verify_with_CDD(CDD) |
|---|
| 498 | True |
|---|
| 499 | |
|---|
| 500 | >>> mintKey == MintKey().fromPython(mintKey.toPython()) |
|---|
| 501 | True |
|---|
| 502 | |
|---|
| 503 | Okay. We'll test verify_time now. It returns (can_mint, can_redeem) |
|---|
| 504 | The tuple input to timegm is (year, month, day, hour, minute, second). |
|---|
| 505 | The h/m/s is the same as on a normal clock. h/m/s is in 24 hour time |
|---|
| 506 | with the day starting at 0:0:0 and the end of the day 24:0:0. I think |
|---|
| 507 | that day1 24:0:0 is the same as day2 0:0:0. |
|---|
| 508 | >>> mintKey.verify_time(timegm((2007,12,1,0,0,0))) |
|---|
| 509 | (False, False) |
|---|
| 510 | |
|---|
| 511 | >>> mintKey.verify_time(timegm((2008,1,1,0,0,0))) |
|---|
| 512 | (True, True) |
|---|
| 513 | |
|---|
| 514 | >>> mintKey.verify_time(timegm((2008,2,1,0,0,0))) |
|---|
| 515 | (True, True) |
|---|
| 516 | |
|---|
| 517 | >>> mintKey.verify_time(timegm((2008,2,1,0,0,1))) |
|---|
| 518 | (False, True) |
|---|
| 519 | |
|---|
| 520 | >>> mintKey.verify_time(timegm((2008,4,1,0,0,0))) |
|---|
| 521 | (False, True) |
|---|
| 522 | |
|---|
| 523 | >>> mintKey.verify_time(timegm((2008,4,1,0,0,1))) |
|---|
| 524 | (False, False) |
|---|
| 525 | """ |
|---|
| 526 | |
|---|
| 527 | fields = ['key_identifier', |
|---|
| 528 | 'currency_identifier', |
|---|
| 529 | 'denomination', |
|---|
| 530 | 'not_before', |
|---|
| 531 | 'key_not_after', |
|---|
| 532 | 'token_not_after', |
|---|
| 533 | 'public_key'] |
|---|
| 534 | |
|---|
| 535 | from crypto import decodeRSAKeyPair |
|---|
| 536 | |
|---|
| 537 | codecs = {'key_identifier':{'encode':base64.b64encode,'decode':base64.b64decode}, |
|---|
| 538 | 'public_key':{'encode':str,'decode':decodeRSAKeyPair}, |
|---|
| 539 | 'not_before':{'encode':encodeTime,'decode':decodeTime}, |
|---|
| 540 | 'key_not_after':{'encode':encodeTime,'decode':decodeTime}, |
|---|
| 541 | 'token_not_after':{'encode':encodeTime,'decode':decodeTime}, |
|---|
| 542 | 'denomination':{'encode':str,'decode':validateIntString}} |
|---|
| 543 | |
|---|
| 544 | def __init__(self, **kwargs): |
|---|
| 545 | ContainerWithSignature.__init__(self, **kwargs) |
|---|
| 546 | self.keytype = kwargs.get('keytype', None) |
|---|
| 547 | if self.denomination and not isinstance(self.denomination, types.StringType): |
|---|
| 548 | raise Exception('Tried to set a denomination that was not a string') |
|---|
| 549 | |
|---|
| 550 | def verify_with_CDD(self, currency_description_document): |
|---|
| 551 | """verify_with_CDD verifies the mint key against the CDD ensuring valid values |
|---|
| 552 | matching the CDD and the signature validity.i |
|---|
| 553 | |
|---|
| 554 | It assumes the CDD is valid. If not, some tests here willl pass (notably the |
|---|
| 555 | signature keyprint check) |
|---|
| 556 | """ |
|---|
| 557 | |
|---|
| 558 | cdd = currency_description_document |
|---|
| 559 | |
|---|
| 560 | if not self.signature: |
|---|
| 561 | return False # if we have no signature, we are not valid (or verifiable) |
|---|
| 562 | |
|---|
| 563 | if self.signature.keyprint != cdd.signature.keyprint: |
|---|
| 564 | return False # if they aren't the same master key, it isn't valid |
|---|
| 565 | |
|---|
| 566 | if self.denomination not in cdd.denominations: |
|---|
| 567 | return False # if we are not a denomination, we aren't valid |
|---|
| 568 | |
|---|
| 569 | if self.currency_identifier != cdd.currency_identifier: |
|---|
| 570 | return False # we have to be using the same currency identifier |
|---|
| 571 | |
|---|
| 572 | if self.key_identifier != cdd.issuer_cipher_suite.hashing(str(self.public_key)).digest(): |
|---|
| 573 | return False # the key identifier is not valid |
|---|
| 574 | |
|---|
| 575 | signing, hashing = cdd.issuer_cipher_suite.signing, cdd.issuer_cipher_suite.hashing |
|---|
| 576 | return self.verifySignature(signing, hashing, cdd.issuer_public_master_key) |
|---|
| 577 | |
|---|
| 578 | def verify_time(self, time): |
|---|
| 579 | """Whether the container is valid at time. Returns a tuple of (can_mint, can_redeem).""" |
|---|
| 580 | |
|---|
| 581 | can_mint = time >= self.not_before and time <= self.key_not_after |
|---|
| 582 | can_redeem = time >= self.not_before and time <= self.token_not_after |
|---|
| 583 | |
|---|
| 584 | return (can_mint, can_redeem) |
|---|
| 585 | |
|---|
| 586 | |
|---|
| 587 | class CurrencyBase(Container): |
|---|
| 588 | """The base class for the currency types. |
|---|
| 589 | |
|---|
| 590 | Test the adding of currencies |
|---|
| 591 | >>> b = CurrencyBase(standard_identifier = 'http://opencoin.org/OpenCoinProtocol/1.0', |
|---|
| 592 | ... currency_identifier = 'http://opencent.net/OpenCent', |
|---|
| 593 | ... denomination = '1', |
|---|
| 594 | ... key_identifier = 'keyid', |
|---|
| 595 | ... serial = '1') |
|---|
| 596 | |
|---|
| 597 | #>>> b.value |
|---|
| 598 | #1 |
|---|
| 599 | >>> import copy |
|---|
| 600 | >>> c = copy.copy(b) |
|---|
| 601 | >>> b + c |
|---|
| 602 | 2 |
|---|
| 603 | >>> sum([b,c]) |
|---|
| 604 | 2 |
|---|
| 605 | |
|---|
| 606 | Test that going from a python copy works |
|---|
| 607 | >>> d = CurrencyBase().fromPython(b.toPython()) |
|---|
| 608 | >>> b + c + d |
|---|
| 609 | 3 |
|---|
| 610 | |
|---|
| 611 | Test proper encoding/decoding of key_identifier and serial |
|---|
| 612 | >>> b = CurrencyBase(standard_identifier = 'http://opencoin.org/OpenCoinProtocol/1.0', |
|---|
| 613 | ... currency_identifier = 'http://opencent.net/OpenCent', |
|---|
| 614 | ... denomination = '1', |
|---|
| 615 | ... key_identifier = 'a', |
|---|
| 616 | ... serial = 'b') |
|---|
| 617 | |
|---|
| 618 | >>> j = b.toJson() |
|---|
| 619 | >>> j |
|---|
| 620 | '[["standard_identifier","http://opencoin.org/OpenCoinProtocol/1.0"],["currency_identifier","http://opencent.net/OpenCent"],["denomination","1"],["key_identifier","YQ=="],["serial","Yg=="]]' |
|---|
| 621 | |
|---|
| 622 | >>> b == CurrencyBase().fromJson(j) |
|---|
| 623 | True |
|---|
| 624 | |
|---|
| 625 | """ |
|---|
| 626 | |
|---|
| 627 | fields = ['standard_identifier', |
|---|
| 628 | 'currency_identifier', |
|---|
| 629 | 'denomination', |
|---|
| 630 | 'key_identifier', |
|---|
| 631 | 'serial'] |
|---|
| 632 | |
|---|
| 633 | # signature codec is in place for CurrencyCoin |
|---|
| 634 | codecs = {'key_identifier':{'encode':base64.b64encode,'decode':base64.b64decode}, |
|---|
| 635 | 'serial':{'encode':base64.b64encode,'decode':base64.b64decode}, |
|---|
| 636 | 'denomination':{'encode':str,'decode':validateIntString}} |
|---|
| 637 | |
|---|
| 638 | |
|---|
| 639 | def __init__(self, **kwargs): |
|---|
| 640 | Container.__init__(self, **kwargs) |
|---|
| 641 | |
|---|
| 642 | def __add__(self,other): |
|---|
| 643 | val = self.getValue() |
|---|
| 644 | if type(other) == type(self) and self.sameCurrency(other): |
|---|
| 645 | return val + other.getValue() |
|---|
| 646 | elif type(other) == int: |
|---|
| 647 | return val + other |
|---|
| 648 | else: |
|---|
| 649 | raise NotImplementedError |
|---|
| 650 | |
|---|
| 651 | |
|---|
| 652 | __radd__ = __add__ |
|---|
| 653 | |
|---|
| 654 | def getValue(self): |
|---|
| 655 | try: |
|---|
| 656 | return int(self.denomination) |
|---|
| 657 | except Exception: |
|---|
| 658 | pass |
|---|
| 659 | |
|---|
| 660 | def sameCurrency(self, other): |
|---|
| 661 | """Verifies the two objects are the same currency.""" |
|---|
| 662 | if self.standard_identifier != other.standard_identifier: |
|---|
| 663 | return False |
|---|
| 664 | |
|---|
| 665 | if self.currency_identifier != other.currency_identifier: |
|---|
| 666 | return False |
|---|
| 667 | |
|---|
| 668 | return True |
|---|
| 669 | |
|---|
| 670 | |
|---|
| 671 | def validate_with_CDD_and_MintKey(self, currency_description_document, mint_key): |
|---|
| 672 | """Validates the currency with the cdd and mint key. Also verifies mint_key (for my safety).""" |
|---|
| 673 | |
|---|
| 674 | cdd = currency_description_document |
|---|
| 675 | |
|---|
| 676 | if not mint_key.verify_with_CDD(cdd): |
|---|
| 677 | return False |
|---|
| 678 | |
|---|
| 679 | if self.standard_identifier != cdd.standard_identifier: |
|---|
| 680 | return False |
|---|
| 681 | |
|---|
| 682 | if self.currency_identifier != mint_key.currency_identifier: |
|---|
| 683 | return False |
|---|
| 684 | |
|---|
| 685 | if self.denomination != mint_key.denomination: |
|---|
| 686 | return False |
|---|
| 687 | |
|---|
| 688 | if self.key_identifier != mint_key.key_identifier: |
|---|
| 689 | return False |
|---|
| 690 | |
|---|
| 691 | return True # Everything checks out |
|---|
| 692 | |
|---|
| 693 | |
|---|
| 694 | |
|---|
| 695 | class CurrencyBlank(CurrencyBase): |
|---|
| 696 | """CurrencyBlank is a blank without signature. It can blind and unblind. |
|---|
| 697 | |
|---|
| 698 | Pieces of this container are messy. It fills its own fields (serial), it |
|---|
| 699 | holds additional information (blind_factor), and it passes that information |
|---|
| 700 | on to the blind algorithm it stores. |
|---|
| 701 | |
|---|
| 702 | Everything in the case of a quick turnaround is easy. You create a blank, |
|---|
| 703 | generate a serial, blind the blank, [receive the signed blind], unblind the |
|---|
| 704 | signature, and make a new coin. |
|---|
| 705 | |
|---|
| 706 | Allowing testing however means that we need to be able to give a blind_factor. |
|---|
| 707 | We also need to be able to stop the transaction partway after sending the |
|---|
| 708 | signed blank, reset the software, and continue the transaction. With this in |
|---|
| 709 | mind, |
|---|
| 710 | |
|---|
| 711 | >>> blank = CurrencyBlank(standard_identifier='http://OpenCoin/1.0/', |
|---|
| 712 | ... currency_identifier='http://OpenCent', |
|---|
| 713 | ... denomination='1', |
|---|
| 714 | ... key_identifier='cent') |
|---|
| 715 | |
|---|
| 716 | >>> blank.toJson() |
|---|
| 717 | Traceback (most recent call last): |
|---|
| 718 | TypeError: b2a_base64... |
|---|
| 719 | |
|---|
| 720 | >>> blank.serial = '123' |
|---|
| 721 | |
|---|
| 722 | >>> blank.toJson() |
|---|
| 723 | '[["standard_identifier","http://OpenCoin/1.0/"],["currency_identifier","http://OpenCent"],["denomination","1"],["key_identifier","Y2VudA=="],["serial","MTIz"]]' |
|---|
| 724 | |
|---|
| 725 | >>> blank.serial = None |
|---|
| 726 | >>> blank.generateSerial() |
|---|
| 727 | >>> base64.b64encode(blank.serial) |
|---|
| 728 | '...' |
|---|
| 729 | |
|---|
| 730 | >>> blank.generateSerial() |
|---|
| 731 | Traceback (most recent call last): |
|---|
| 732 | Exception: Cannot generate a new serial when the serial is set. |
|---|
| 733 | |
|---|
| 734 | |
|---|
| 735 | >>> from calendar import timegm |
|---|
| 736 | >>> from tests import CDD, CDD_private, keys512, addSignature |
|---|
| 737 | >>> import crypto, copy |
|---|
| 738 | |
|---|
| 739 | >>> def addSignatureAndVerify(mintKey, CDD, signing_key): |
|---|
| 740 | ... ics = CDD.issuer_cipher_suite |
|---|
| 741 | ... mintKey = addSignature(mintKey, ics.hashing, ics.signing, |
|---|
| 742 | ... signing_key, mintKey.key_identifier) |
|---|
| 743 | ... return mintKey.verify_with_CDD(CDD) |
|---|
| 744 | |
|---|
| 745 | >>> private1 = keys512[0] |
|---|
| 746 | >>> public1 = private1.newPublicKeyPair() |
|---|
| 747 | |
|---|
| 748 | >>> hash_alg = CDD.issuer_cipher_suite.hashing |
|---|
| 749 | >>> sign_alg = CDD.issuer_cipher_suite.signing |
|---|
| 750 | >>> blind_alg = CDD.issuer_cipher_suite.blinding |
|---|
| 751 | |
|---|
| 752 | >>> mintKey1 = MintKey(key_identifier=public1.key_id(hash_alg), |
|---|
| 753 | ... currency_identifier='http://opencent.net/OpenCent', |
|---|
| 754 | ... denomination='1', |
|---|
| 755 | ... not_before=timegm((2008,1,1,0,0,0)), |
|---|
| 756 | ... key_not_after=timegm((2008,2,1,0,0,0)), |
|---|
| 757 | ... token_not_after=timegm((2008,4,1,0,0,0)), |
|---|
| 758 | ... public_key=public1) |
|---|
| 759 | >>> mintKey1 = addSignature(mintKey1, hash_alg, sign_alg, CDD_private, CDD.signature.keyprint) |
|---|
| 760 | |
|---|
| 761 | >>> mintKey1.verify_with_CDD(CDD) |
|---|
| 762 | True |
|---|
| 763 | |
|---|
| 764 | >>> private5 = keys512[1] |
|---|
| 765 | >>> public5 = private5.newPublicKeyPair() |
|---|
| 766 | >>> mintKey5 = MintKey(key_identifier=public5.key_id(hash_alg), |
|---|
| 767 | ... currency_identifier='http://opencent.net/OpenCent', |
|---|
| 768 | ... denomination='5', |
|---|
| 769 | ... not_before=timegm((2008,1,1,0,0,0)), |
|---|
| 770 | ... key_not_after=timegm((2008,2,1,0,0,0)), |
|---|
| 771 | ... token_not_after=timegm((2008,4,1,0,0,0)), |
|---|
| 772 | ... public_key=public5) |
|---|
| 773 | >>> mintKey5 = addSignature(mintKey5, hash_alg, sign_alg, CDD_private, CDD.signature.keyprint) |
|---|
| 774 | |
|---|
| 775 | >>> mintKey5.verify_with_CDD(CDD) |
|---|
| 776 | True |
|---|
| 777 | |
|---|
| 778 | >>> blank = CurrencyBlank(standard_identifier=CDD.standard_identifier, |
|---|
| 779 | ... currency_identifier=CDD.currency_identifier, |
|---|
| 780 | ... denomination='1', |
|---|
| 781 | ... key_identifier=mintKey1.key_identifier, |
|---|
| 782 | ... serial='abcdefghijklmnopqrstuvwxyz') |
|---|
| 783 | |
|---|
| 784 | >>> blank.toJson() |
|---|
| 785 | '[["standard_identifier","http://opencoin.org/OpenCoinProtocol/1.0"],["currency_identifier","http://opencent.net/OpenCent"],["denomination","1"],["key_identifier","sj17RxE1hfO06+oTgBs9Z7xLut/3NN+nHJbXSJYTks0="],["serial","YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo="]]' |
|---|
| 786 | |
|---|
| 787 | >>> blank.content_part() == blank.toJson() |
|---|
| 788 | True |
|---|
| 789 | |
|---|
| 790 | >>> blind_value = blank.blind_blank(CDD, mintKey1, blind_factor='blind factor') |
|---|
| 791 | >>> base64.b64encode(blind_value) |
|---|
| 792 | 'FtDSI1eT2FsXK+R/zJVk9mGTRan4KQsogVmSMFts3kVrdV4y3mkIuYEaZ3B0ZP491rqR0QtIlVEAYf8sQNgbhQ==' |
|---|
| 793 | |
|---|
| 794 | Do some magic as the mint |
|---|
| 795 | >>> blind_sig = sign_alg(private1).sign(blind_value) |
|---|
| 796 | >>> base64.b64encode(blind_sig) |
|---|
| 797 | 'ZEDz4RHVwUByR+QXwgZcNeIyg9T3hAgxl0taabVKjbv0DbxTmHYK9fgqlCtBSAvmNntk03DMKrIPpaLuAV3UcA==' |
|---|
| 798 | |
|---|
| 799 | >>> clear_sig = blank.unblind_signature(blind_sig) |
|---|
| 800 | >>> base64.b64encode(clear_sig) |
|---|
| 801 | 'HIck+fim0TkjVupU1AeKpuSGN1CxLnDmT2jpBHMZSgdpYhKE90XoAsQVznljEn4NTXvRs5cXslWUNvcUeAuv2A==' |
|---|
| 802 | |
|---|
| 803 | Check for the same signature |
|---|
| 804 | >>> clear_sig == sign_alg(private1).sign(hash_alg(blank.content_part()).digest()) |
|---|
| 805 | True |
|---|
| 806 | |
|---|
| 807 | >>> coin = blank.newCoin(clear_sig) # perform seperate testing |
|---|
| 808 | >>> coin.validate_with_CDD_and_MintKey(CDD, mintKey1) |
|---|
| 809 | True |
|---|
| 810 | |
|---|
| 811 | >>> coin.toJson() |
|---|
| 812 | '[["standard_identifier","http://opencoin.org/OpenCoinProtocol/1.0"],["currency_identifier","http://opencent.net/OpenCent"],["denomination","1"],["key_identifier","sj17RxE1hfO06+oTgBs9Z7xLut/3NN+nHJbXSJYTks0="],["serial","YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo="],["signature","HIck+fim0TkjVupU1AeKpuSGN1CxLnDmT2jpBHMZSgdpYhKE90XoAsQVznljEn4NTXvRs5cXslWUNvcUeAuv2A=="]]' |
|---|
| 813 | |
|---|
| 814 | >>> coin2 = blank.newCoin(clear_sig, CDD, mintKey1) |
|---|
| 815 | |
|---|
| 816 | Test newCoin failures... newCoin performs a check if given a CDD |
|---|
| 817 | or a MintKey. if it does not validate, it will raise an Exception. |
|---|
| 818 | If we only give a MintKey or a CDD, it attempts to perform the |
|---|
| 819 | validation, although it will fail. This is to prevent any accidents |
|---|
| 820 | in arguments where the coin will not be checked when we want it to. |
|---|
| 821 | >>> coin3 = blank.newCoin(clear_sig, CDD, mintKey5) |
|---|
| 822 | Traceback (most recent call last): |
|---|
| 823 | BlankError: New coin does not validate! |
|---|
| 824 | |
|---|
| 825 | >>> coin4 = blank.newCoin(clear_sig, CDD) |
|---|
| 826 | Traceback (most recent call last): |
|---|
| 827 | AttributeError: ... |
|---|
| 828 | |
|---|
| 829 | >>> coin5 = blank.newCoin(clear_sig, mint_key=mintKey1) |
|---|
| 830 | Traceback (most recent call last): |
|---|
| 831 | AttributeError: ... |
|---|
| 832 | |
|---|
| 833 | Test other blank functions. |
|---|
| 834 | >>> blank2 = CurrencyBlank(standard_identifier=CDD.standard_identifier, |
|---|
| 835 | ... currency_identifier=CDD.currency_identifier, |
|---|
| 836 | ... denomination='1', |
|---|
| 837 | ... key_identifier=mintKey1.key_identifier, |
|---|
| 838 | ... serial='abcdefghijklmnopqrstuvwxyz') |
|---|
| 839 | >>> blank2.setBlind(blind_alg, mintKey1.public_key, 'blind factor') |
|---|
| 840 | >>> base64.b64encode(blank2.unblind_signature(blind_sig)) |
|---|
| 841 | 'HIck+fim0TkjVupU1AeKpuSGN1CxLnDmT2jpBHMZSgdpYhKE90XoAsQVznljEn4NTXvRs5cXslWUNvcUeAuv2A==' |
|---|
| 842 | |
|---|
| 843 | >>> blank3 = CurrencyBlank(standard_identifier=CDD.standard_identifier, |
|---|
| 844 | ... currency_identifier=CDD.currency_identifier, |
|---|
| 845 | ... denomination='1', |
|---|
| 846 | ... key_identifier=mintKey1.key_identifier, |
|---|
| 847 | ... serial='abcdefghijklmnopqrstuvwxyz', |
|---|
| 848 | ... blind_factor='blind factor') |
|---|
| 849 | >>> blank3.setBlind(blind_alg, mintKey1.public_key) |
|---|
| 850 | >>> base64.b64encode(blank3.unblind_signature(blind_sig)) |
|---|
| 851 | 'HIck+fim0TkjVupU1AeKpuSGN1CxLnDmT2jpBHMZSgdpYhKE90XoAsQVznljEn4NTXvRs5cXslWUNvcUeAuv2A==' |
|---|
| 852 | |
|---|
| 853 | And this is the normal and standard way to make a blank and blind |
|---|
| 854 | >>> blank4 = CurrencyBlank(standard_identifier=CDD.standard_identifier, |
|---|
| 855 | ... currency_identifier=CDD.currency_identifier, |
|---|
| 856 | ... denomination='1', |
|---|
| 857 | ... key_identifier=mintKey1.key_identifier) |
|---|
| 858 | >>> blank4.generateSerial() |
|---|
| 859 | >>> blind_value4 = blank4.blind_blank(CDD, mintKey1) |
|---|
| 860 | >>> base64.b64encode(blind_value4) |
|---|
| 861 | '...' |
|---|
| 862 | |
|---|
| 863 | The blind_factor can be used to recreate a blank |
|---|
| 864 | >>> from Crypto.Util import number |
|---|
| 865 | >>> base64.b64encode(number.long_to_bytes(blank4.blind_factor)) |
|---|
| 866 | '...' |
|---|
| 867 | |
|---|
| 868 | """ |
|---|
| 869 | |
|---|
| 870 | def __init__(self, **kwargs): |
|---|
| 871 | CurrencyBase.__init__(self, **kwargs) |
|---|
| 872 | self.blind_factor = kwargs.get('blind_factor', None) |
|---|
| 873 | |
|---|
| 874 | def generateSerial(self): |
|---|
| 875 | from crypto import _r as Random |
|---|
| 876 | if self.serial: |
|---|
| 877 | raise Exception('Cannot generate a new serial when the serial is set.') |
|---|
| 878 | |
|---|
| 879 | self.serial = Random.getRandomString(128) |
|---|
| 880 | |
|---|
| 881 | def blind_blank(self, cdd, mint_key, blind_factor=None): |
|---|
| 882 | """Returns the blinded value of the hash of the coin for signing.""" |
|---|
| 883 | |
|---|
| 884 | if self.blind_factor: # we only allow blind_factors to be passed in. |
|---|
| 885 | raise Exception('CurrencyBlank already has a blind factor') |
|---|
| 886 | |
|---|
| 887 | self.blinding = cdd.issuer_cipher_suite.blinding(mint_key.public_key) |
|---|
| 888 | hashing = cdd.issuer_cipher_suite.hashing(self.content_part()) |
|---|
| 889 | |
|---|
| 890 | # Note: we pass in blind_factor. If None, the blind_factor will be automaticall generated |
|---|
| 891 | self.blind_value, self.blind_factor = self.blinding.blind(hashing.digest(), blind_factor) |
|---|
| 892 | return self.blind_value |
|---|
| 893 | |
|---|
| 894 | def unblind_signature(self, signature): |
|---|
| 895 | """Returns the unblinded value of the blinded signature.""" |
|---|
| 896 | |
|---|
| 897 | return self.blinding.unblind(signature) |
|---|
| 898 | |
|---|
| 899 | def setBlind(self, blind_alg, key, blind_factor=None): |
|---|
| 900 | """setBlind is used to set the state of self.blinding without performing the blinding. |
|---|
| 901 | It is similar to blind_blank except that it does not perform any blinding itself. |
|---|
| 902 | """ |
|---|
| 903 | |
|---|
| 904 | if blind_factor: |
|---|
| 905 | self.blind_factor = blind_factor |
|---|
| 906 | |
|---|
| 907 | self.blinding = blind_alg(key, self.blind_factor) |
|---|
| 908 | |
|---|
| 909 | def newCoin(self, signature, currency_description_document=None, mint_key=None): |
|---|
| 910 | """Returns a coin using the unblinded signature. |
|---|
| 911 | Performs tests if currency_description_document and mint_key are provided. |
|---|
| 912 | """ |
|---|
| 913 | coin = CurrencyCoin(standard_identifier = self.standard_identifier, |
|---|
| 914 | currency_identifier = self.currency_identifier, |
|---|
| 915 | denomination = self.denomination, |
|---|
| 916 | key_identifier = self.key_identifier, |
|---|
| 917 | serial = self.serial, |
|---|
| 918 | signature = signature) |
|---|
| 919 | |
|---|
| 920 | |
|---|
| 921 | # if only one is provided, we have an error. Purposefully use an 'or' for the test to get an exception later |
|---|
| 922 | if currency_description_document or mint_key: |
|---|
| 923 | if not coin.validate_with_CDD_and_MintKey(currency_description_document, |
|---|
| 924 | mint_key): |
|---|
| 925 | raise BlankError('New coin does not validate!') |
|---|
| 926 | |
|---|
| 927 | return coin |
|---|
| 928 | |
|---|
| 929 | class BlankError(Exception): |
|---|
| 930 | """BlankError is when a Blank cannot become a coin.""" |
|---|
| 931 | pass |
|---|
| 932 | |
|---|
| 933 | class CurrencyCoin(CurrencyBase): |
|---|
| 934 | """The CurrencyCoin. |
|---|
| 935 | |
|---|
| 936 | >>> from tests import CDD, CDD_private, keys512, addSignature, mintKeys |
|---|
| 937 | >>> import copy |
|---|
| 938 | >>> from calendar import timegm |
|---|
| 939 | |
|---|
| 940 | >>> private = keys512[0] |
|---|
| 941 | >>> public = private.newPublicKeyPair() |
|---|
| 942 | |
|---|
| 943 | >>> hash_alg = CDD.issuer_cipher_suite.hashing |
|---|
| 944 | >>> sign_alg = CDD.issuer_cipher_suite.signing |
|---|
| 945 | >>> blind_alg = CDD.issuer_cipher_suite.blinding |
|---|
| 946 | |
|---|
| 947 | >>> mintKey = mintKeys[0] |
|---|
| 948 | |
|---|
| 949 | >>> signature = 'HIck+fim0TkjVupU1AeKpuSGN1CxLnDmT2jpBHMZSgdpYhKE90XoAsQVznljEn4NTXvRs5cXslWUNvcUeAuv2A==' |
|---|
| 950 | |
|---|
| 951 | >>> coin = CurrencyCoin(standard_identifier=CDD.standard_identifier, |
|---|
| 952 | ... currency_identifier=CDD.currency_identifier, |
|---|
| 953 | ... denomination='1', |
|---|
| 954 | ... key_identifier=mintKey.key_identifier, |
|---|
| 955 | ... serial='abcdefghijklmnopqrstuvwxyz', |
|---|
| 956 | ... signature=base64.b64decode(signature)) |
|---|
| 957 | |
|---|
| 958 | >>> coin.toJson() |
|---|
| 959 | '[["standard_identifier","http://opencoin.org/OpenCoinProtocol/1.0"],["currency_identifier","http://opencent.net/OpenCent"],["denomination","1"],["key_identifier","sj17RxE1hfO06+oTgBs9Z7xLut/3NN+nHJbXSJYTks0="],["serial","YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo="],["signature","HIck+fim0TkjVupU1AeKpuSGN1CxLnDmT2jpBHMZSgdpYhKE90XoAsQVznljEn4NTXvRs5cXslWUNvcUeAuv2A=="]]' |
|---|
| 960 | |
|---|
| 961 | >>> coin.content_part() |
|---|
| 962 | '[["standard_identifier","http://opencoin.org/OpenCoinProtocol/1.0"],["currency_identifier","http://opencent.net/OpenCent"],["denomination","1"],["key_identifier","sj17RxE1hfO06+oTgBs9Z7xLut/3NN+nHJbXSJYTks0="],["serial","YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo="]]' |
|---|
| 963 | |
|---|
| 964 | >>> coin2 = CurrencyCoin().fromJson(coin.toJson()) |
|---|
| 965 | >>> coin2 == coin |
|---|
| 966 | True |
|---|
| 967 | |
|---|
| 968 | >>> coin.validate_with_CDD_and_MintKey(CDD, mintKey) |
|---|
| 969 | True |
|---|
| 970 | |
|---|
| 971 | Check that a bad signature will fail |
|---|
| 972 | >>> coin3 = copy.deepcopy(coin) |
|---|
| 973 | >>> coin3.signature = 'foobar' |
|---|
| 974 | >>> coin3.validate_with_CDD_and_MintKey(CDD, mintKey) |
|---|
| 975 | False |
|---|
| 976 | |
|---|
| 977 | """ |
|---|
| 978 | |
|---|
| 979 | def __init__(self, **kwargs): |
|---|
| 980 | CurrencyBase.__init__(self, **kwargs) |
|---|
| 981 | self.jsontext = None |
|---|
| 982 | self.signature = kwargs.get('signature') |
|---|
| 983 | |
|---|
| 984 | # base64 encoding and decoding is hardcoded here in to/fromJson |
|---|
| 985 | def toJson(self,extranames=1): |
|---|
| 986 | if extranames: |
|---|
| 987 | if self.jsontext: |
|---|
| 988 | return self.jsontext |
|---|
| 989 | else: |
|---|
| 990 | data = self.toPython(forcesig=1) |
|---|
| 991 | self.jsontext = json.write(data) |
|---|
| 992 | return self.jsontext |
|---|
| 993 | else: |
|---|
| 994 | return json.write(self.toPython(nosig=1)) |
|---|
| 995 | |
|---|
| 996 | def fromJson(self,text): |
|---|
| 997 | data = json.read(text) |
|---|
| 998 | if len(data) == len(self.fields) + 1 and data[-1][0] == 'signature': |
|---|
| 999 | self.jsontext = text |
|---|
| 1000 | return self.fromPython(data) |
|---|
| 1001 | |
|---|
| 1002 | def toPython(self,forcesig=None,nosig=None): |
|---|
| 1003 | data = CurrencyBase.toPython(self) |
|---|
| 1004 | if forcesig or (not nosig and self.signature): |
|---|
| 1005 | data.append(('signature',base64.b64encode(self.signature))) |
|---|
| 1006 | return data |
|---|
| 1007 | |
|---|
| 1008 | def fromPython(self,data): |
|---|
| 1009 | CurrencyBase.fromPython(self,data) |
|---|
| 1010 | if len(data) == len(self.fields) + 1 and data[-1][0] == 'signature': |
|---|
| 1011 | self.signature = base64.b64decode(data[-1][1]) |
|---|
| 1012 | return self |
|---|
| 1013 | |
|---|
| 1014 | |
|---|
| 1015 | def validate_with_CDD_and_MintKey(self, currency_description_document, mint_key): |
|---|
| 1016 | |
|---|
| 1017 | if not CurrencyBase.validate_with_CDD_and_MintKey(self, currency_description_document, mint_key): |
|---|
| 1018 | return False |
|---|
| 1019 | |
|---|
| 1020 | key = mint_key.public_key |
|---|
| 1021 | signer = currency_description_document.issuer_cipher_suite.signing(key) |
|---|
| 1022 | hasher = currency_description_document.issuer_cipher_suite.hashing(self.content_part()) |
|---|
| 1023 | |
|---|
| 1024 | if not signer.verify(hasher.digest(), self.signature): |
|---|
| 1025 | return False |
|---|
| 1026 | |
|---|
| 1027 | return True |
|---|
| 1028 | |
|---|
| 1029 | |
|---|
| 1030 | if __name__ == "__main__": |
|---|
| 1031 | import doctest |
|---|
| 1032 | doctest.testmod(optionflags=doctest.ELLIPSIS) |
|---|