Advertisement
Guest User

Untitled

a guest
Mar 19th, 2016
268
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 18.01 KB | None | 0 0
  1. import unirest
  2. from bitcoin.core import x, Hash160
  3. from bitcoin.core.key import CPubKey
  4. from ecdsa import SigningKey, VerifyingKey, SECP256k1, BadSignatureError
  5. from collections import OrderedDict
  6. import ecdsa.util
  7. import base64
  8. import binascii
  9. import re
  10. import hashlib
  11. import time
  12. import json
  13. import urllib
  14. import os
  15.  
  16. """
  17. Takes an unknown string and returns bytes.
  18. * If the string is a hex string it will be decoded.
  19. * If the string is base64 encoded it will be decoded.
  20. """
  21. def auto_bytes(s):
  22. #None.
  23. if s == None:
  24. return s
  25.  
  26. #Already bytes.
  27. if type(s) == type(b""):
  28. return s
  29.  
  30. #Hex string.
  31. if re.match("^[#][0-9a-fA-F]+$", s) != None and not ((len(s) - 1) % 2):
  32. return binascii.unhexlify(s[1:])
  33.  
  34. #Base64 string.
  35. if re.match("^[a-zA-Z0-9+=/]+$", s) != None:
  36. try:
  37. return base64.b64decode(s)
  38. except:
  39. pass
  40.  
  41. return s.encode("ascii")
  42.  
  43. def pow_mod(x, y, z):
  44. "Calculate (x ** y) % z efficiently."
  45. number = 1
  46. while y:
  47. if y & 1:
  48. number = number * x % z
  49. y >>= 1
  50. x = x * x % z
  51. return number
  52.  
  53. class ECDSA:
  54. def __init__(self, public_key=None, private_key=None):
  55. self.id = 0
  56. if public_key is not None:
  57. public_key = "#" + public_key
  58.  
  59. if private_key is not None:
  60. private_key = "#" + private_key
  61.  
  62. self.public_key = auto_bytes(public_key)
  63. self.private_key = private_key
  64. self.addr_version = None
  65. self.use_compression = 1
  66. if private_key == "":
  67. private_key = None
  68.  
  69. #Generate key pairs.
  70. if self.public_key == None and private_key == None:
  71. self.sign_key = SigningKey.generate(curve=SECP256k1)
  72. self.verify_key = self.sign_key.get_verifying_key()
  73. self.private_key = self.sign_key.to_string()
  74. self.public_key = self.verify_key.to_string()
  75. return
  76.  
  77. #Init public key.
  78. self.old_verify_str = None
  79. if self.public_key != None:
  80. self.public_key = self.parse_public_key(self.public_key)
  81. self.verify_key = VerifyingKey.from_string(self.public_key, SECP256k1)
  82. self.sign_key = None
  83. self.old_verify_str = self.verify_key.to_string()
  84.  
  85. #Init private key.
  86. if self.private_key != None:
  87. #Construct keys from private key.
  88. self.private_key = self.parse_private_key(private_key)
  89. self.sign_key = SigningKey.from_string(self.private_key, SECP256k1)
  90. self.verify_key = self.sign_key.get_verifying_key()
  91.  
  92. #Check private key corrosponds to public key.
  93. if self.old_verify_str != None:
  94. if self.old_verify_str != self.verify_key.to_string():
  95. raise Exception("Private key doesn't corrospond to stored public key.")
  96.  
  97. #Input = b58check || b64 || hex private key
  98. #Output = binary ECDSA private key.
  99. def parse_private_key(self, private_key):
  100. if type(private_key) == str:
  101. #Base58 string.
  102. if re.match("^[!][" + b58 + "]+$", private_key) != None:
  103. try:
  104. priv, version, compressed, checksum = PrivateKey().wif_to_private(private_key[1:])
  105. self.addr_version = version
  106. return priv
  107. except:
  108. pass
  109.  
  110. return auto_bytes(private_key)
  111.  
  112. #Input = b64encoded public key.
  113. #Output = ECDSA-style (non-prefixed) decompressed public key.
  114. def parse_public_key(self, public_key):
  115. public_key = auto_bytes(public_key)
  116.  
  117. #Key is valid compressed key.
  118. if len(public_key) == 64:
  119. return public_key
  120.  
  121. #Key is valid but contains prefix.
  122. if len(public_key) == 65:
  123. return public_key[1:]
  124.  
  125. #Invalid compressed key.
  126. if len(public_key) == 32:
  127. raise Exception("Prefix byte for compressed pub key not known.")
  128. #public_key = bytes([3]) + public_key
  129.  
  130. #Decompress the key.
  131. if len(public_key) == 33:
  132. return self.decompress_public_key(public_key)[1:]
  133.  
  134. return public_key
  135.  
  136. #Input: str, str
  137. #Output: str b64 encoded sig
  138. def sign(self, msg, private_key=None):
  139. if private_key == None:
  140. sign_key = self.sign_key
  141. else:
  142. private_key = self.parse_private_key(private_key)
  143. sign_key = SigningKey.from_string(private_key, SECP256k1)
  144.  
  145. if sign_key == None:
  146. raise Exception("Private key for ECDSA keypair not known.")
  147.  
  148. if type(msg) == str:
  149. msg = msg.encode("ascii")
  150.  
  151. return base64.b64encode(sign_key.sign(msg, sigencode=ecdsa.util.sigencode_der, hashfunc=hashlib.sha256)).decode("utf-8")
  152.  
  153. #Input: b64 encoded pub key, b64 encoded sig, str msg
  154. #Output: boolean
  155. def valid_signature(self, signature, msg, public_key=None):
  156. try:
  157. if type(msg) == str:
  158. msg = msg.encode("ascii")
  159.  
  160. #Parse public key.
  161. if public_key == None:
  162. public_key = self.public_key
  163. else:
  164. public_key = self.parse_public_key(public_key)
  165.  
  166. signature = auto_bytes(signature)
  167. msg = auto_bytes(msg)
  168. verify_key = VerifyingKey.from_string(public_key, SECP256k1)
  169. return verify_key.verify(signature, msg)
  170. except Exception as e:
  171. print(e)
  172. return 0
  173.  
  174. #Input: compressed prefixed pub key bytes
  175. #Output: decompressed prefixed pub key bytes
  176. def decompress_public_key(self, public_key):
  177. #https://bitcointalk.org/index.php?topic=644919.0
  178. public_key = auto_bytes(public_key)
  179. public_key = binascii.hexlify(public_key)
  180. public_key = public_key.decode("utf-8")
  181.  
  182. p = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f
  183. y_parity = int(public_key[:2]) - 2
  184. x = int(public_key[2:], 16)
  185.  
  186. a = (pow_mod(x, 3, p) + 7) % p
  187. y = pow_mod(a, (p+1)//4, p)
  188. if y % 2 != y_parity:
  189. y = -y % p
  190. x = "{0:0{1}x}".format(x, 64)
  191. y = "{0:0{1}x}".format(y, 64)
  192.  
  193. ret = "04" + x + y
  194.  
  195. return binascii.unhexlify(ret)
  196.  
  197. #Input: decompressed prefixed pub key bytes
  198. #Output: decompressed prefixed pub key bytes
  199. def compress_public_key(self, public_key):
  200. #https://bitcointalk.org/index.php?topic=644919.0
  201. public_key = auto_bytes(public_key)
  202. public_key = binascii.hexlify(public_key).decode("utf-8")
  203.  
  204. #Is there a prefix byte?
  205. if len(public_key) == 128:
  206. offset = 0
  207. else:
  208. offset = 2
  209.  
  210. #Extract X from X, Y public key.
  211. x = int(public_key[offset:64 + offset], 16)
  212. y = int(public_key[65 + offset:], 16)
  213.  
  214. if y % 2:
  215. prefix = "03"
  216. else:
  217. prefix = "02"
  218.  
  219. #Return compressed public key.
  220. ret = prefix + "{0:0{1}x}".format(x, 64)
  221. return binascii.unhexlify(ret)
  222.  
  223. #Input: prefixed pub key bytes
  224. #Output: boolean
  225. def validate_public_key(self, public_key):
  226. public_key = auto_bytes(public_key)
  227. public_key = binascii.hexlify(public_key).decode("utf-8")
  228. prefix = public_key[0:2]
  229. if prefix == "04":
  230. is_compressed = 0
  231. else:
  232. is_compressed = 1
  233.  
  234. return is_compressed
  235.  
  236. #Returns a Bitcoin style public_key (prefixed)
  237. def get_public_key(self, f="hex", public_key=None):
  238. if public_key == None:
  239. public_key = self.public_key
  240.  
  241. #O4 = uncompressed.
  242. public_key_hex = "04" + binascii.hexlify(public_key).decode("utf-8")
  243. if self.use_compression:
  244. public_key = binascii.unhexlify(public_key_hex)
  245. public_key = self.compress_public_key(public_key)
  246. public_key_hex = binascii.hexlify(public_key).decode("utf-8")
  247. else:
  248. public_key = binascii.unhexlify(public_key_hex)
  249.  
  250. cpub = CPubKey(x(public_key_hex))
  251.  
  252. if f == "bin":
  253. return public_key
  254.  
  255. elif f == "hex":
  256. return public_key_hex.decode("utf-8")
  257.  
  258. elif f == "cpub":
  259. return cpub
  260.  
  261. elif f == "hash":
  262. return Hash160(cpub)
  263.  
  264. else:
  265. return base64.b64encode(public_key).decode("utf-8")
  266.  
  267. def get_private_key(self, f="hex"):
  268. if f == "bin":
  269. return self.private_key
  270.  
  271. elif f == "hex":
  272. return binascii.hexlify(self.private_key).decode("utf-8")
  273.  
  274. elif f == "wif":
  275. if self.addr_version == None:
  276. raise Exception("Addr version for priv key not known.")
  277. priv, version, compressed, checksum = PrivateKey().private_to_wif(self.private_key, self.addr_version, self.use_compression)
  278. return priv
  279.  
  280. else:
  281. return base64.b64encode(self.private_key).decode("utf-8")
  282.  
  283. ############################################
  284. # Begin monkey patch for unirest.delete.
  285.  
  286. def patched_delete(url, **kwargs):
  287. params = unirest.get_parameters(kwargs)
  288. if len(params) > 0:
  289. if url.find("?") == -1:
  290. url += "?"
  291. else:
  292. url += "&"
  293. url += unirest.utils.dict2query(dict((k, v) for k, v in params.iteritems() if v is not None)) # Removing None values/encode unicode objects
  294.  
  295. return unirest.__dorequest("DELETE", url, {}, kwargs.get(unirest.HEADERS_KEY, {}), kwargs.get(unirest.AUTH_KEY, None), kwargs.get(unirest.CALLBACK_KEY, None))
  296.  
  297. unirest.delete = patched_delete
  298.  
  299. # End monkey patch for unirest.delete.
  300. ############################################
  301.  
  302. def REST(method, resource, params={}, auth=(), headers={}, callback=None):
  303. # API location.
  304. endpoint = "https://api.metadisk.org"
  305.  
  306. # ASYNC request handlers.
  307. method_handlers = {
  308. "GET": unirest.get,
  309. "POST": unirest.post,
  310. "DELETE": unirest.delete,
  311. "PATCH": unirest.patch,
  312. "PUT": unirest.put
  313. }
  314.  
  315. # Find handler for HTTP method.
  316. handler = method_handlers[method]
  317.  
  318. # Callback wrapper.
  319. if callback is not None:
  320. wrapper = lambda response: callback(response.body)
  321. else:
  322. wrapper = None
  323.  
  324. # Add nonce to params.
  325. # Should prob be UUID for future versions.
  326. params["__nonce"] = time.time()
  327. if method is not "PUT":
  328. content = json.dumps(params, separators=(',', ':'))
  329. else:
  330. content = params
  331.  
  332. # Build content.
  333. if method in ["GET", "DELETE"]:
  334. # For GET and DELETE -- the content to push gets
  335. # added to the URL as a query string instead of as
  336. # a post body. This tells the request library to
  337. # use the same order as the payload we sign later on.
  338. content = payload = json.loads(content, object_pairs_hook=OrderedDict)
  339. payload = urllib.urlencode(payload)
  340.  
  341. # Set authentication (use basic or not.)
  342. if isinstance(auth, User):
  343. basic_auth = (auth.username, auth.password_hash())
  344. else:
  345. basic_auth = ()
  346.  
  347. # Set authentication (ECDSA sig)
  348. url = endpoint + resource
  349. headers["Accept"] = "application/json"
  350. headers["Content-Type"] = "application/json"
  351. if isinstance(auth, ECDSA) and "x-token" not in headers:
  352. # Build contract.
  353. if method not in ["GET", "DELETE"]:
  354. payload = json.dumps(params, separators=(',', ':'))
  355. contract = "\n".join([
  356. method,
  357. resource,
  358. payload
  359. ])
  360.  
  361. print(contract)
  362.  
  363. # Sign contract.
  364. sig = auth.sign(contract)
  365. sig = base64.b64decode(sig)
  366. sig = binascii.hexlify(sig)
  367.  
  368. # Set headers.
  369. headers["x-pubkey"] = auth.get_public_key("hex")
  370. headers["x-signature"] = sig
  371.  
  372. print(basic_auth)
  373. print(params)
  374. print(url)
  375. print(method)
  376. print(content)
  377. print(headers)
  378. print(handler)
  379.  
  380. # Make call.
  381. ret = handler(
  382. url,
  383. headers=headers,
  384. params=content,
  385. callback=wrapper,
  386. auth=basic_auth
  387. )
  388.  
  389. # Async = get response object
  390. # Sync = get HTTP response
  391. if callback is None:
  392. return ret.body
  393. else:
  394. return ret
  395.  
  396. class User():
  397. def __init__(self, username, password=None):
  398. rand = lambda: hashlib.sha256(os.urandom(15)).hexdigest()
  399. self.username = username
  400. self.password = password or rand()
  401.  
  402. def password_hash(self):
  403. return hashlib.sha256(self.password).hexdigest()
  404.  
  405. def register(self, callback=None):
  406. password = self.password_hash()
  407. params = {
  408. "email": self.username,
  409. "password": password
  410. }
  411.  
  412. return REST("POST", "/users", params, callback=callback)
  413.  
  414. def activate(self, token, callback=None):
  415. resource = "/activations/%s" % token
  416.  
  417. return REST("GET", resource, callback=callback)
  418.  
  419. # A list of ECDSA objects for a user account.
  420. # Requires HTTP basic auth.
  421. class Keys():
  422. def __init__(self, user):
  423. self.user = user
  424. self.key_pairs = []
  425.  
  426. def list(self, callback=None):
  427. resource = "/keys"
  428. ret = REST(
  429. "GET",
  430. resource,
  431. callback=callback,
  432. auth=self.user
  433. )
  434.  
  435. return ret
  436.  
  437. # Hex ECDSA pub key.
  438. def create(self, key_pair=None, callback=None):
  439. key_pair = key_pair or ECDSA()
  440. if key_pair not in self.key_pairs:
  441. self.key_pairs.append(key_pair)
  442.  
  443. params = {
  444. "key": key_pair.get_public_key("hex")
  445. }
  446.  
  447. ret = REST(
  448. "POST",
  449. "/keys",
  450. params,
  451. callback=callback,
  452. auth=self.user
  453. )
  454.  
  455. return ret
  456.  
  457. def delete(self, key_pair, callback=None):
  458. if key_pair in self.key_pairs:
  459. self.key_pairs.remove(key_pair)
  460.  
  461. resource = "/keys/%s" % key_pair.get_public_key("hex")
  462. ret = REST(
  463. "DELETE",
  464. resource,
  465. callback=callback,
  466. auth=self.user
  467. )
  468.  
  469. return ret
  470.  
  471. # A list of bucket objects for a user account.
  472. class Buckets():
  473. def __init__(self, key_pair):
  474. self.key_pair = key_pair
  475.  
  476. def create(self, name, storage, transfer, key_pairs, callback=None):
  477. # Get pub keys.
  478. pub_keys = []
  479. for key_pair in key_pairs:
  480. pub_keys.append(key_pair.get_public_key("hex"))
  481.  
  482. params = {
  483. "name": name,
  484. "storage": int(storage),
  485. "transfer": int(transfer),
  486. "pubkeys": pub_keys
  487. }
  488.  
  489. ret = REST(
  490. "POST",
  491. "/buckets",
  492. params,
  493. callback=callback,
  494. auth=self.key_pair
  495. )
  496.  
  497. return ret
  498.  
  499. def list(self, callback=None):
  500. resource = "/buckets"
  501. ret = REST(
  502. "GET",
  503. resource,
  504. callback=callback,
  505. auth=self.key_pair
  506. )
  507.  
  508. return ret
  509.  
  510. class Files():
  511. def __init__(self, bucket_id, key_pair):
  512. self.bucket_id = bucket_id
  513. self.key_pair = key_pair
  514.  
  515. def list(self, callback=None):
  516. resource = "/buckets/%s/files" % self.bucket_id
  517. ret = REST(
  518. "GET",
  519. resource,
  520. callback=callback,
  521. auth=self.key_pair
  522. )
  523.  
  524. return ret
  525.  
  526. def token(self, operation, callback=None):
  527. params = {
  528. "id": self.bucket_id,
  529. "operation": operation
  530. }
  531.  
  532. ret = REST(
  533. "POST",
  534. "/buckets/%s/tokens" % self.bucket_id,
  535. params,
  536. callback=callback,
  537. auth=self.key_pair
  538. )
  539.  
  540. return ret
  541.  
  542. def upload(self, path, token, callback=None):
  543. file_size = os.path.getsize(path)
  544. params = {
  545. "id": self.bucket_id,
  546. "file": open(path, mode="r")
  547. }
  548.  
  549. headers = {
  550. "x-token": token,
  551. "x-filesize": file_size
  552. }
  553.  
  554. ret = REST(
  555. "PUT",
  556. "/buckets/%s/files" % self.bucket_id,
  557. params,
  558. headers=headers,
  559. callback=callback,
  560. auth=self.key_pair
  561. )
  562.  
  563. return ret
  564.  
  565.  
  566. def download(self, file_hash, token, callback=None):
  567. resource = "/buckets/%s/files/%s" % (
  568. self.bucket_id,
  569. file_hash
  570. )
  571.  
  572. headers = {
  573. "x-token": token
  574. }
  575.  
  576. ret = REST(
  577. "GET",
  578. resource,
  579. headers=headers,
  580. callback=callback,
  581. auth=self.key_pair
  582. )
  583.  
  584. return ret
  585.  
  586. # A bucket is also an object that can store + download files.
  587. class Bucket():
  588. def __init__(self, bucket_id, key_pair):
  589. self.bucket_id = bucket_id
  590. self.key_pair = key_pair
  591. self.files = Files(self.bucket_id, self.key_pair)
  592.  
  593. def list(self, callback=None):
  594. resource = "/buckets/%s" % self.bucket_id
  595. ret = REST(
  596. "GET",
  597. resource,
  598. callback=callback,
  599. auth=self.key_pair
  600. )
  601.  
  602. return ret
  603.  
  604. def delete(self, callback=None):
  605. resource = "/buckets/%s" % self.bucket_id
  606. ret = REST(
  607. "DELETE",
  608. resource,
  609. callback=callback,
  610. auth=self.key_pair
  611. )
  612.  
  613. return ret
  614.  
  615. def patch(self, params, callback=None):
  616. resource = "/buckets/%s" % self.bucket_id
  617. ret = REST(
  618. "PATCH",
  619. resource,
  620. params=params,
  621. callback=callback,
  622. auth=self.key_pair
  623. )
  624.  
  625. return ret
  626.  
  627.  
  628. class API():
  629. def __init__(self):
  630. self.user = User()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement