def getKeys(secret, seed=None): """ Generate keyring containing public key, signing and checking keys as attribute. Keyword arguments: secret (str or bytes) -- a human pass phrase seed (byte) -- a sha256 sequence bytes (private key actualy) Return dict """ if secret and not isinstance(secret, bytes): secret = secret.encode('utf-8') seed = hashlib.sha256(secret).digest() if not seed else seed signingKey = SigningKey.from_secret_exponent( int(binascii.hexlify(seed), 16), SECP256k1, hashlib.sha256) publicKey = signingKey.get_verifying_key().to_string() return { "publicKey": hexlify( compressEcdsaPublicKey(publicKey) if cfg.compressed else publicKey ), "privateKey": hexlify(signingKey.to_string()), "wif": getWIF(seed) }
def test_get_bytes_and_hexlify(self): self.assertEqual( TestArkCrypto.signed_tx0_hex, bin_.hexlify( dposlib.core.crypto.getBytes(TestArkCrypto.signed_tx0_dict))) self.assertEqual( TestArkCrypto.signSigned_tx0_hex, bin_.hexlify( dposlib.core.crypto.getBytes( TestArkCrypto.signSigned_tx0_dict)))
def multiSignWithKey(self, privateKey): """ Add a signature in `signatures` field according to given index and privateKey. Args: privateKey (str): private key as hex string. """ # remove id if any and set fee if needed self.pop("id", False) if "fee" not in self: setFees(self) # get public key from private key publicKey = dposlib.core.crypto.secp256k1.PublicKey.from_seed( unhexlify(privateKey)) publicKey = hexlify(publicKey.encode()) # create a multi-signature signature = dposlib.core.crypto.getSignatureFromBytes( serialize(self, exclude_sig=True, exclude_multi_sig=True, exclude_second_sig=True), privateKey) # add multisignature in transaction try: self.appendMultiSignature(publicKey, signature) except Exception: raise ValueError("public key %s not allowed here" % publicKey)
def getSignatureFromBytes(data, privateKey): """ Generate signature from data using private key. Args: data (bytes): bytes sequence privateKey (str): private key as hex string Returns: signature as hex string """ secret0 = unhexlify(privateKey) msg = secp256k1.hash_sha256(data) if bytearray(data)[0] == 0xff: return hexlify(schnorr.bcrypto410_sign(msg, secret0)) else: return hexlify(ecdsa.rfc6979_sign(msg, secret0, canonical=True))
def test_transaction_sign(self): tx = dposlib.core.Transaction(TestArkCrypto.tx0_dict) tx.link(self.secret, self.secondSecret) tx.sign() if tx.get("version", 1) >= 2: return None self.assertEqual(tx["signature"], TestArkCrypto.signed_tx0_dict["signature"]) self.assertEqual(bin_.hexlify(dposlib.core.crypto.getBytes(tx)), TestArkCrypto.signed_tx0_hex) tx.signSign() self.assertEqual(tx["signSignature"], TestArkCrypto.signSigned_tx0_dict["signSignature"]) self.assertEqual(bin_.hexlify(dposlib.core.crypto.getBytes(tx)), TestArkCrypto.signSigned_tx0_hex) tx.unlink()
def getSignature(tx, private): return hexlify( crypto_sign( hashlib.sha256(getBytes(tx)).digest(), unhexlify(private) )[:crypto_sign_BYTES] )
def getIdFromBytes(data): """ Generate data id. Arguments: data (bytes) -- data in bytes Return str """ return hexlify(hashlib.sha256(data).digest())
def getIdFromBytes(data): """ Generate data id. Args: data (bytes): data as bytes sequence Returns: id as hex string """ return hexlify(secp256k1.hash_sha256(data))
def getId(tx): """ Generate transaction id. Arguments: tx (dict) -- a transaction description Return str """ return hexlify(hashlib.sha256(getBytes(tx)).digest())
def remoteSignWithSecret(network, ms_publicKey, txid, secret=None): return remoteSignWithKey( network, ms_publicKey, txid, hexlify( secp256k1.hash_sha256( secret if secret is not None else getpass.getpass("secret > ") ) ) )
def setUpClass(self): prikey0 = secp256k1.hash_sha256("secret") self.publicKey = hexlify( secp256k1.PublicKey.from_seed(prikey0).encode()) self.privateKey = hexlify(prikey0) self.msg_der = "00993c0b0003a02b9d5fdd1307c2ee4652ba54d492d1fd11a7d1b"\ "b3f3a44c4a05e79f19de9331eb8dd0e799b69714269474be623ce"\ "b3309020dff5756e69746573743a20747820776974682073696d7"\ "06c65207369676e61747572650000000000000000000000000000"\ "000000000000000000000000000000000000e1f50500000000c04"\ "b030000000000" self.msg_raw = "ff0217010000000000010000000000000003a02b9d5fdd1307c2e"\ "e4652ba54d492d1fd11a7d1bb3f3a44c4a05e79f19de933809698"\ "000000000000a08601000000000000000000171dfc69b54c7fe90"\ "1e91d5a9ab78388645e2427ea" self.der = "304402200ee92c78a690844eaabf6833ed4fe9c66db1476bcfaad1754"\ "aec780116aa16b0022045e8fda963191c1df485ff18966df509679d8d"\ "dd47e6fd51f2645309227fc5c9" self.raw = "4f01bd21828a633a3c821b9984fe642deab87237b99e62a543ca6948f"\ "f1d6d32f2475ada1f933da0591c40603693614afa69fcb4caa2b4be01"\ "8788de9f10c42a"
def htlcSecret(secret): """ Compute an HTLC secret hex string from passphrase. Arguments: secret (str): passphrase Returns: transaction object """ return hexlify( hashlib.sha256(secret if isinstance(secret, bytes) else secret. encode("utf-8")).digest()[:16])
def test_serialization(self): for tx in self.fixtures[:]: serialized = tx.pop("serialized", False) id_ = tx.pop("id", False) sig = tx.pop("signature", False) t = tx_.Transaction(tx) t.signature = sig computed = bin_.hexlify( dposlib.core.crypto.getBytes( t, exclude_multi_sig=not t['type'] == 4)) self.assertEqual(str(serialized), computed) self.assertEqual(id_, dposlib.core.crypto.getId(t))
def wifSignatureFromBytes(data, wif): """ Generate signature from data using WIF key. Args: data (bytes): bytes sequence wif (str): wif key Returns: signature """ seed = base58.b58decode_check( str(wif) if not isinstance(wif, bytes) else wif)[1:33] return getSignatureFromBytes(data, hexlify(seed))
def getKeys(secret): """ Generate keyring containing secp256k1 keys-pair and wallet import format (WIF). Args: secret (str, bytes or int): anything that could issue a private key on secp256k1 curve Returns: public, private and WIF keys """ if isinstance(secret, (str, bytes, unicode)): try: seed = unhexlify(secret) except Exception: seed = secp256k1.hash_sha256(secret) else: seed = secp256k1.bytes_from_int(secret) publicKey = secp256k1.PublicKey.from_seed(seed) return { "publicKey": hexlify(publicKey.encode()), "privateKey": hexlify(seed), "wif": getWIF(seed) }
def getSignature(tx, privateKey): """ Generate transaction signature using private key. Arguments: tx (dict) -- a transaction description privateKey (str) -- a private key as hex string Return str """ signingKey = SigningKey.from_string(unhexlify(privateKey), SECP256k1, hashlib.sha256) return hexlify( signingKey.sign_deterministic(getBytes(tx), hashlib.sha256, sigencode=sigencode_der_canonize))
def getSignatureFromBytes(data, privateKey): """ Generate data signature using private key. Arguments: data (bytes) -- data in bytes privateKey (str) -- a private key as hex string Return str """ signingKey = SigningKey.from_string(unhexlify(privateKey), SECP256k1, hashlib.sha256) return hexlify( signingKey.sign_deterministic(data, hashlib.sha256, sigencode=sigencode_der_canonize))
def sendApdu(apdus, debug=True): dongle = getDongle(debug) try: for apdu in apdus: data = bytes(dongle.exchange(apdu, timeout=30)) except CommException as comm: if comm.sw == 0x6985: sys.stdout.write("Rejected by user\n") elif comm.sw in [0x6D00, 0x6F00, 0x6700]: sys.stdout.write("Make sure your Ledger is connected and unlocked " "with the ARK app opened\n") else: sys.stdout.write("%r\n" % comm) data = b"" finally: dongle.close() return hexlify(data)
def getMultiSignaturePublicKey(minimum, *publicKeys): """ Compute ARK multi signature public key according to [ARK AIP #18]( https://github.com/ArkEcosystem/AIPs/blob/master/AIPS/aip-18.md ). Args: minimum (int): minimum signature required publicKeys (list of str): public key list Returns: the multisignature public key """ if 2 > minimum > len(publicKeys): raise ValueError("min signatures value error") P = secp256k1.PublicKey.from_secret("%02x" % minimum) for publicKey in publicKeys: P = P + secp256k1.PublicKey.decode(unhexlify(publicKey)) return hexlify(P.encode())
def getPublicKey(dongle_path, debug=False): """ Compute the public key associated to a derivation path. Argument: dongle_path -- value returned by parseBip32Path Keyword argument: debug -- flag to activate debug messages from ledger key [default: False] Return str (hex) """ apdu = buildPkeyApdu(dongle_path) dongle = getDongle(debug) data = bytes(dongle.exchange(apdu, timeout=30)) dongle.close() len_pkey = util.basint(data[0]) return util.hexlify(data[1:len_pkey + 1])
def multiSignWithKey(self, privateKey): """ Add a signature in `signatures` field according to given index and privateKey. Args: privateKey (str): private key as hex string """ # get public key from private key publicKey = dposlib.core.crypto.secp256k1.PublicKey.from_seed( unhexlify(privateKey)) publicKey = hexlify(publicKey.encode()) # get public key index : # if type 4 find index in asset # else find it in _multisignature attribute if self["type"] == 4: index = self.asset["multiSignature"]["publicKeys"].index(publicKey) elif self._multisignature: if publicKey not in self._multisignature["publicKeys"]: raise ValueError("public key %s not allowed here" % publicKey) index = self._multisignature["publicKeys"].index(publicKey) else: raise Exception("multisignature not to be used here") # remove id if any and set fee self.pop("id", False) if "fee" not in self: self.setFee() # concatenate index and signature and fill it in signatures field # sorted(set([...])) returns sorted([...]) with unique values self["signatures"] = sorted(set( self.get("signatures", []) + [ "%02x" % index + dposlib.core.crypto.getSignatureFromBytes( serialize(self, exclude_sig=True, exclude_multi_sig=True, exclude_second_sig=True), privateKey) ]), key=lambda s: s[:2])
def remoteSignWithKey(network, ms_publicKey, txid, privateKey): publicKey = hexlify( secp256k1.PublicKey.from_seed(unhexlify(privateKey)).encode() ) wallet = getWallet(network, ms_publicKey).get("data", {}) if txid in wallet: options = {} if wallet[txid]["type"] == 4 else { "exclude_sig": True, "exclude_multi_sig": True, "exclude_second_sig": True } return putSignature( network, ms_publicKey, txid, publicKey, crypto.getSignatureFromBytes( crypto.getBytes(wallet[txid], **options), privateKey ) ) else: raise Exception("%s transaction not found" % txid)
def getSignature(data, dongle_path, debug=False): """ Get ledger Nano S signature of given transaction. Argument: data -- transaction as bytes data returned by dposlib.core.crypto.getBytes dongle_path -- value returned by parseBip32Path Keyword argument: debug -- flag to activate debug messages from ledger key [default: False] Return str (hex) """ apdu1, apdu2 = buildTxApdu(dongle_path, data) dongle = getDongle(debug) result = dongle.exchange(bytes(apdu1), timeout=30) if apdu2: apdu = util.unhexlify("e0048140") + util.intasb(len(apdu2)) + apdu2 result = dongle.exchange(bytes(apdu), timeout=30) dongle.close() return util.hexlify(result)
def getSerial(network, ms_publicKey, txid): """ ``GET /multisignature/{network}/{ms_publicKey}/{txid}/serial`` endpoint. Return specific pending transaction serial from a specific public key. """ if network != getattr(rest.cfg, "network", False): rest.use(network) if flask.request.method != "GET": return json.dumps({ "success": False, "API error": "GET request only allowed here" }) tx = load(network, ms_publicKey, txid) if tx: return json.dumps({ "success": True, "data": hexlify(crypto.getBytes(tx)) }), 200 else: return json.dumps({"success": False})
def htlcLock(amount, address, secret, expiration=24, vendorField=None): """ Build an HTLC lock transaction. Emoji can be included in transaction vendorField using unicode formating. ```python >>> vendorField = u"message with sparkles \u2728" ``` Args: amount (float): transaction amount in ark. address (str): valid recipient address. secret (str): lock passphrase. expiration (float): transaction validity in hour. vendorField (str): vendor field message. Returns: dposlib.ark.tx.Transaction: orphan transaction. """ return Transaction( version=cfg.txversion, type=8, amount=amount*100000000, recipientId=address, vendorField=vendorField, asset={ "lock": { "secretHash": hexlify( hashlib.sha256(htlcSecret(secret).encode("utf-8")).digest() ), "expiration": { "type": 1, "value": int(slots.getTime() + expiration*60*60) } } } )
class Transaction(dict): """ A python `dict` that implements all the necessities to manually generate valid transactions. """ FMULT = None FEESL = None # custom properties definitions datetime = property( lambda cls: slots.getRealTime(cls["timestamp"]), None, None, "Transaction timestamp returned as python datetime object") fee = property( lambda cls: cls.get("fee", None), lambda cls, value: setFees(cls, value), None, ) vendorField = property( lambda cls: cls.get("vendorField", None), lambda cls, value: setVendorField(cls, value), lambda cls: setVendorField(cls, ""), ) vendorFieldHex = property( lambda cls: hexlify(cls.get("vendorField", "").encode("utf-8")), lambda cls, value: setVendorFieldHex(cls, value), lambda cls: setVendorField(cls, ""), ) timestamp = property(lambda cls: cls.get("timestamp", None), lambda cls, value: setTimestamp(cls, value), None, "Transaction timestamp setter") recipientId = recipient = property( lambda cls: cls.get("recipientId", None), lambda cls, value: cls._setitem("recipientId", checkAddress(value)), lambda cls: cls.pop("recipientId", None), "Receiver address checker and setter") senderId = sender = property( lambda cls: cls.get("senderId", None), lambda cls, value: cls._setitem("senderId", checkAddress(value)), lambda cls: cls.pop("senderId", None), "Sender address checker and setter") senderPublicKey = property( lambda cls: cls.get("senderPublicKey", None), lambda cls, value: setSenderPublicKey(cls, value), lambda cls: deleteSenderPublicKey(cls), "Initialize transaction according to senderPublicKey value") secondSignature = signSignature = property( lambda cls: cls.get("signSignature", None), lambda cls, value: cls._setitem("signSignature", value), lambda cls: cls.pop("signSignature", None), "Second signature") #: If `True` then `amount` + `fee` = total arktoshi flow feeIncluded = property( lambda cls: "_amount" in cls.__dict__, lambda cls, value: (setFeeIncluded if bool(value) else unsetFeeIncluded)(cls), None, "if `True` then `amount` + `fee` = total arktoshi flow") @staticmethod def useDynamicFee(value="minFee"): """ Activate and configure dynamic fees parameters. Value can be either an integer defining the fee multiplier constant or a string defining the fee level to use acccording to the 30-days-average. possible values are `avgFee` `minFee` (default) and `maxFee`. Args: value (str or int): constant or fee multiplier. """ if hasattr(cfg, "doffsets"): if isinstance(value, (int, float)): Transaction.FMULT = int(value) Transaction.FEESL = None elif value in ["maxFee", "avgFee", "minFee"]: Transaction.FMULT = None Transaction.FEESL = value else: Transaction.FMULT = None Transaction.FEESL = None else: Transaction.FMULT = None Transaction.FEESL = None setDynamicFee = useDynamicFee useStaticFee = setStaticFee = lambda self: self.useDynamicFee(None) # private definitions def _setitem(self, item, value): try: cast = dposlib.core.TYPING[item] except KeyError: dict.__setattr__(self, item, value) else: if not isinstance(value, cast): value = cast(value) dict.__setitem__(self, item, value) def _reset(self): """remove data linked to validation process.""" self.pop("signature", False) self.pop("signatures", False) self.pop("signSignature", False) self.pop("secondSignature", False) self.pop("id", False) def __init__(self, *args, **kwargs): self._ignore_bad_fields = kwargs.pop("ignore_bad_fields", False) self.FEESL = kwargs.pop("FEESL", Transaction.FEESL) self.FMULT = kwargs.pop("FMULT", Transaction.FMULT) # initialize a void dict dict.__init__(self) # if blockchain package loaded merge all elements else return void dict data = dict(*args, **kwargs) last_to_be_set = [(k, data.pop(k, None)) for k in [ "fee", "nonce", "signatures", "signature", "signSignature", "secondSignature", "id" ]] # set default values dict.__setitem__(self, "version", data.pop("version", 2)) dict.__setitem__(self, "network", getattr(cfg, "pubkeyHash", 30)) dict.__setitem__(self, "typeGroup", data.pop("typeGroup", 1)) dict.__setitem__(self, "amount", int(data.pop("amount", 0))) dict.__setitem__(self, "type", data.pop("type", 0)) dict.__setitem__(self, "asset", data.pop("asset", {})) # initialize all non-void fields for key, value in [(k, v) for k, v in list(data.items()) + last_to_be_set if v is not None]: if key == "fee": value = int(value) self[key] = value def __setitem__(self, item, value): if item == "secret": self.link(value) elif item == "secondSecret": self.link(None, value) else: try: dict.__getattribute__(self, item) except AttributeError: self._setitem(item, value) else: object.__setattr__(self, item, value) __setattr__ = __setitem__ def __getattr__(self, attr): try: return dict.__getitem__(self, attr) except KeyError: return dict.__getattribute__(self, attr) __getitem__ = __getattr__ def __str__(self): return json.dumps(OrderedDict(sorted(self.items(), key=lambda e: e[0])), indent=2) def __repr__(self): return "<Blockchain transaction type %(typeGroup)s:%(type)s>" % self def link(self, secret=None, secondSecret=None): """ Save public and private keys derived from secrets. This is equivalent to wallet login. it limits number of secret keyboard entries. Args: secret (str): passphrase. secondSecret (str): second passphrase. """ if hasattr(dposlib, "core"): if secret: keys = dposlib.core.crypto.getKeys(secret) self.senderPublicKey = keys["publicKey"] self._privateKey = keys["privateKey"] if secondSecret: keys = dposlib.core.crypto.getKeys(secondSecret) self._secondPrivateKey = keys["privateKey"] def unlink(self): try: deleteSenderPublicKey(self) del self._privateKey del self._secondPrivateKey except Exception: pass def touch(self): if hasattr(self, "_publicKey"): self.senderPublicKey = self._publicKey # root sign function called by others def sign(self): """ Generate the `signature` field. Private key have to be set first. """ self._reset() if hasattr(self, "_privateKey"): if "fee" not in self: setFees(self) if self.type == 4: missings = \ self.asset["multiSignature"]["min"] - \ len(self.get("signature", [])) if missings: raise Exception("owner signature missing (%d)" % missings) self["signature"] = dposlib.core.crypto.getSignature( self, self._privateKey) else: raise Exception("orphan transaction can not sign itsef") # root second sign function called by others def signSign(self): """ Generate the `signSignature` field. Transaction have to be signed and second private key have to be set first. """ if "signature" in self: # or "signatures" in self ? self.pop("id", False) try: self["signSignature"] = dposlib.core.crypto.getSignature( self, self._secondPrivateKey, exclude_second_sig=True, ) except AttributeError: raise Exception("no second private Key available") else: raise Exception("transaction not signed") # sign functions using passphrases def signWithSecret(self, secret): """ Generate the `signature` field using passphrase. The associated public and private keys are stored till `dposlib.ark.unlink` is called. Args: secret (`str`): passphrase. """ self.link(secret) self.sign() def signSignWithSecondSecret(self, secondSecret): """ Generate the `signSignature` field using second passphrase. The associated second public and private keys are stored till `dposlib.ark.unlink` is called. Args: secondSecret (`str`): second passphrase. """ self.link(None, secondSecret) self.signSign() def multiSignWithSecret(self, secret): """ Add a signature in `signatures` field. Args: index (int): signature index. secret (str): passphrase. """ keys = dposlib.core.crypto.getKeys(secret) self.multiSignWithKey(keys["privateKey"]) # sign function using crypto keys def signWithKeys(self, publicKey, privateKey): """ Generate the `signature` field using public and private keys. They are stored till `dposlib.ark.unlink` is called. Args: publicKey (str): public key as hex string. privateKey (str): private key as hex string. """ if self.get("senderPublicKey", None) != publicKey: self.senderPublicKey = publicKey self._privateKey = privateKey self.sign() def signSignWithKey(self, secondPrivateKey): """ Generate the `signSignature` field using second private key. It is stored till `dposlib.ark.unlink` is called. Args: secondPrivateKey (`str`): second private key as hex string. """ self._secondPrivateKey = secondPrivateKey self.signSign() def multiSignWithKey(self, privateKey): """ Add a signature in `signatures` field according to given index and privateKey. Args: privateKey (str): private key as hex string. """ # remove id if any and set fee if needed self.pop("id", False) if "fee" not in self: setFees(self) # get public key from private key publicKey = dposlib.core.crypto.secp256k1.PublicKey.from_seed( unhexlify(privateKey)) publicKey = hexlify(publicKey.encode()) # create a multi-signature signature = dposlib.core.crypto.getSignatureFromBytes( serialize(self, exclude_sig=True, exclude_multi_sig=True, exclude_second_sig=True), privateKey) # add multisignature in transaction try: self.appendMultiSignature(publicKey, signature) except Exception: raise ValueError("public key %s not allowed here" % publicKey) def appendMultiSignature(self, publicKey, signature): # if type 4 find index in asset if self["type"] == 4: index = self.asset["multiSignature"]["publicKeys"].index(publicKey) # else find it in _multisignature attribute elif self._multisignature: index = self._multisignature["publicKeys"].index(publicKey) else: raise Exception("multisignature not to be used here") # concatenate index and signature and fill it in signatures field # sorted(set([...])) returns sorted([...]) with unique values self["signatures"] = sorted( set(self.get("signatures", []) + ["%02x" % index + signature]), key=lambda s: s[:2]) def identify(self): """Generate the `id` field. Transaction have to be signed.""" if "signature" in self or "signatures" in self: if len(self._multisignature): missings = \ self._multisignature["min"] - \ len(self.get("signatures", [])) if missings: raise Exception("owner signature missing (%d)" % missings) elif self._secondPublicKey: if "signSignature" not in self: raise Exception("second signature is missing") self.pop("id", False) self["id"] = dposlib.core.crypto.getIdFromBytes( serialize(self, exclude_multi_sig=False)) else: raise Exception("transaction not signed") def finalize(self, secret=None, secondSecret=None, fee=None, fee_included=False): """ Finalize a transaction by setting `fee`, signatures and `id`. Args: secret (str): passphrase. secondSecret (str): second passphrase. fee (int): manually set fee value in `arktoshi`. fee_included (bool): see `dposlib.ark.tx.Transaction.feeIncluded`. """ self.link(secret, secondSecret) # automatically set fees if needed if "fee" not in self or fee is not None: self.fee = fee self.feeIncluded = fee_included # sign with private keys # if transaction is not from a multisignature wallet if not self._multisignature: self.sign() if hasattr(self, "_secondPrivateKey"): self.signSign() # generate the id self.identify()
def getKeys(secret, seed=None): if not isinstance(secret, bytes): secret = secret.encode('utf-8') seed = hashlib.sha256(secret).digest() if not seed else seed publicKey, privateKey = list(hexlify(e) for e in crypto_sign_seed_keypair(seed)) return {"publicKey": publicKey, "privateKey": privateKey}