def _calc_initial(self): Hpoint = self.setup._ecdsa_H HGpoint = self.setup._ecdsa_HG k = self.nonce a = self.amount_mod # We don't want to calculate (a * Hpoint) since the time to execute # would reveal information about size / bitcount of a. So, we use # the nonce as a blinding offset factor. Ppoint = ((a - k) % order) * Hpoint + k * HGpoint if Ppoint == ecdsa.ellipticcurve.INFINITY: raise ResultAtInfinity self.P_uncompressed = point_to_ser(Ppoint, comp=False) self.P_compressed = point_to_ser(Ppoint, comp=True)
def sign_message(self, sequence, message, password): sig = None try: message = message.encode('utf8') inputPath = self.get_derivation() + "/%d/%d" % sequence msg_hash = Hash(msg_magic(message)) inputHash = to_hexstr(msg_hash) hasharray = [] hasharray.append({'hash': inputHash, 'keypath': inputPath}) hasharray = json.dumps(hasharray) msg = b'{"sign":{"meta":"sign message", "data":%s}}' % hasharray.encode('utf8') dbb_client = self.plugin.get_client(self) if not dbb_client.is_paired(): raise Exception(_("Could not sign message.")) reply = dbb_client.hid_send_encrypt(msg) self.handler.show_message(_("Signing message ...") + "\n\n" + _("To continue, touch the Digital Bitbox's blinking light for 3 seconds.") + "\n\n" + _("To cancel, briefly touch the blinking light or wait for the timeout.")) reply = dbb_client.hid_send_encrypt(msg) # Send twice, first returns an echo for smart verification (not implemented) self.handler.finished() if 'error' in reply: raise Exception(reply['error']['message']) if 'sign' not in reply: raise Exception(_("Could not sign message.")) if 'recid' in reply['sign'][0]: # firmware > v2.1.1 sig = bytes([27 + int(reply['sign'][0]['recid'], 16) + 4]) + binascii.unhexlify(reply['sign'][0]['sig']) pk, compressed = pubkey_from_signature(sig, msg_hash) pk = point_to_ser(pk.pubkey.point, compressed) addr = public_key_to_p2pkh(pk) if verify_message(addr, sig, message) is False: raise Exception(_("Could not sign message")) elif 'pubkey' in reply['sign'][0]: # firmware <= v2.1.1 for i in range(4): sig = bytes([27 + i + 4]) + binascii.unhexlify(reply['sign'][0]['sig']) try: addr = public_key_to_p2pkh(binascii.unhexlify(reply['sign'][0]['pubkey'])) if verify_message(addr, sig, message): break except Exception: continue else: raise Exception(_("Could not sign message")) except BaseException as e: self.give_error(e) return sig
def verify_signature(self, signature, message, verification_key): "This method verifies signature of message" try: pk, compressed = pubkey_from_signature(signature, Hash(msg_magic(message))) pubkey = point_to_ser(pk.pubkey.point, compressed).hex() return pubkey == verification_key except Exception as e: self.print_error("verify_signature:", repr(e)) return False
def decrypt(data, privkey): """ Decrypt using the private key; returns (message, key) or raises DecryptionFailed on failure. """ if len(data) < 33 + 16 + 16: # key, at least 1 block, and mac raise DecryptionFailed try: nonce_pub = ser_to_point(data[:33]) except: raise DecryptionFailed sec = int.from_bytes(privkey, 'big') key = hashlib.sha256(point_to_ser(sec * nonce_pub, comp=True)).digest() return decrypt_with_symmkey(data, key), key
def encrypt(message, pubkey, pad_to_length=None): """ pad_to_length must be a multiple of 16, and equal to or larger than len(message)+4. Default is to choose the smallest possible value. If the `pubkey` is not a valid point, raises EncryptionFailed. """ try: pubpoint = ser_to_point(pubkey) except: raise EncryptionFailed nonce_sec = ecdsa.util.randrange(order) nonce_pub = point_to_ser(nonce_sec * G, comp=True) key = hashlib.sha256(point_to_ser(nonce_sec * pubpoint, comp=True)).digest() plaintext = len(message).to_bytes(4, 'big') + message if pad_to_length is None: plaintext += b'\0' * (-len(plaintext) % 16) else: if pad_to_length % 16 != 0: raise ValueError(f'{pad_to_length} not multiple of 16') need = pad_to_length - len(plaintext) if need < 0: raise ValueError(f'{pad_to_length} < {len(plaintext)}') plaintext += b'\0' * need iv = b'\0' * 16 if AES: ciphertext = AES.new(key, AES.MODE_CBC, iv).encrypt(plaintext) else: aes_cbc = pyaes.AESModeOfOperationCBC(key, iv=iv) aes = pyaes.Encrypter(aes_cbc, padding=pyaes.PADDING_NONE) ciphertext = aes.feed( plaintext) + aes.feed() # empty aes.feed() flushes buffer mac = hmacdigest(key, ciphertext, 'sha256')[:16] return nonce_pub + ciphertext + mac
def add_points(points_iterable): """ Adds one or more serialized points together. This is fastest if the points are already uncompressed. Returns uncompressed point. Note: intermediate sums are allowed to be the point at infinity, but not the final result. """ plist = [] if seclib: ctx = seclib.ctx for pser in points_iterable: P_buf = create_string_buffer(64) _b = bytes(pser) res = seclib.secp256k1_ec_pubkey_parse(ctx, P_buf, _b, c_size_t(len(_b))) if not res: raise ValueError( 'point could not be parsed by the secp256k1 library') plist.append(P_buf) if not plist: raise ValueError('empty list') num = len(plist) result_buf = create_string_buffer(64) publist = (c_void_p * num)(*(cast(x, c_void_p) for x in plist)) res = seclib.secp256k1_ec_pubkey_combine(ctx, result_buf, publist, num) if res != 1: raise ResultAtInfinity serpoint = create_string_buffer(65) sersize = c_size_t(65) res = seclib.secp256k1_ec_pubkey_serialize( ctx, serpoint, byref(sersize), result_buf, secp256k1.SECP256K1_EC_UNCOMPRESSED) assert res == 1 assert sersize.value == 65 return serpoint.raw else: for pser in points_iterable: plist.append(ser_to_point(pser)) if not plist: raise ValueError('empty list') Psum = sum(plist[1:], plist[0]) if Psum == ecdsa.ellipticcurve.INFINITY: raise ResultAtInfinity return point_to_ser(Psum, comp=False)
def sign_transaction(self, tx, password, *, use_cache=False): if tx.is_complete(): return try: p2pkhTransaction = True derivations = self.get_tx_derivations(tx) inputhasharray = [] hasharray = [] pubkeyarray = [] # Build hasharray from inputs for i, txin in enumerate(tx.inputs()): if txin['type'] == 'coinbase': self.give_error("Coinbase not supported") # should never happen if txin['type'] != 'p2pkh': p2pkhTransaction = False for x_pubkey in txin['x_pubkeys']: if x_pubkey in derivations: index = derivations.get(x_pubkey) inputPath = "%s/%d/%d" % (self.get_derivation(), index[0], index[1]) inputHash = Hash(binascii.unhexlify(tx.serialize_preimage(i))) hasharray_i = {'hash': to_hexstr(inputHash), 'keypath': inputPath} hasharray.append(hasharray_i) inputhasharray.append(inputHash) break else: self.give_error("No matching x_key for sign_transaction") # should never happen # Build pubkeyarray from outputs for _type, address, amount in tx.outputs(): info = tx.output_info.get(address) if info is not None: index, xpubs, m, script_type = info changePath = self.get_derivation() + "/%d/%d" % index changePubkey = self.derive_pubkey(index[0], index[1]) pubkeyarray_i = {'pubkey': changePubkey, 'keypath': changePath} pubkeyarray.append(pubkeyarray_i) # Special serialization of the unsigned transaction for # the mobile verification app. # At the moment, verification only works for p2pkh transactions. if p2pkhTransaction: class CustomTXSerialization(Transaction): @classmethod def input_script(self, txin, estimate_size=False, sign_schnorr=False): if txin['type'] == 'p2pkh': return Transaction.get_preimage_script(txin) if txin['type'] == 'p2sh': # Multisig verification has partial support, but is disabled. This is the # expected serialization though, so we leave it here until we activate it. return '00' + push_script(Transaction.get_preimage_script(txin)) raise Exception("unsupported type %s" % txin['type']) tx_dbb_serialized = CustomTXSerialization(tx.serialize()).serialize() else: # We only need this for the signing echo / verification. tx_dbb_serialized = None # Build sign command dbb_signatures = [] steps = math.ceil(1.0 * len(hasharray) / self.maxInputs) for step in range(int(steps)): hashes = hasharray[step * self.maxInputs : (step + 1) * self.maxInputs] msg = { "sign": { "data": hashes, "checkpub": pubkeyarray, }, } if tx_dbb_serialized is not None: msg["sign"]["meta"] = to_hexstr(Hash(tx_dbb_serialized)) msg = json.dumps(msg).encode('ascii') dbb_client = self.plugin.get_client(self) if not dbb_client.is_paired(): raise Exception("Could not sign transaction.") reply = dbb_client.hid_send_encrypt(msg) if 'error' in reply: raise Exception(reply['error']['message']) if 'echo' not in reply: raise Exception("Could not sign transaction.") if self.plugin.is_mobile_paired() and tx_dbb_serialized is not None: reply['tx'] = tx_dbb_serialized self.plugin.comserver_post_notification(reply) if steps > 1: self.handler.show_message(_("Signing large transaction. Please be patient ...") + "\n\n" + _("To continue, touch the Digital Bitbox's blinking light for 3 seconds.") + " " + _("(Touch {} of {})").format((step + 1), steps) + "\n\n" + _("To cancel, briefly touch the blinking light or wait for the timeout.") + "\n\n") else: self.handler.show_message(_("Signing transaction...") + "\n\n" + _("To continue, touch the Digital Bitbox's blinking light for 3 seconds.") + "\n\n" + _("To cancel, briefly touch the blinking light or wait for the timeout.")) # Send twice, first returns an echo for smart verification reply = dbb_client.hid_send_encrypt(msg) self.handler.finished() if 'error' in reply: if reply["error"].get('code') in (600, 601): # aborted via LED short touch or timeout raise UserCancelled() raise Exception(reply['error']['message']) if 'sign' not in reply: raise Exception("Could not sign transaction.") dbb_signatures.extend(reply['sign']) # Fill signatures if len(dbb_signatures) != len(tx.inputs()): raise Exception("Incorrect number of transactions signed.") # Should never occur for i, txin in enumerate(tx.inputs()): num = txin['num_sig'] for pubkey in txin['pubkeys']: signatures = list(filter(None, txin['signatures'])) if len(signatures) == num: break # txin is complete ii = txin['pubkeys'].index(pubkey) signed = dbb_signatures[i] if 'recid' in signed: # firmware > v2.1.1 recid = int(signed['recid'], 16) s = binascii.unhexlify(signed['sig']) h = inputhasharray[i] pk = MyVerifyingKey.from_signature(s, recid, h, curve = SECP256k1) pk = to_hexstr(point_to_ser(pk.pubkey.point, True)) elif 'pubkey' in signed: # firmware <= v2.1.1 pk = signed['pubkey'] if pk != pubkey: continue sig_r = int(signed['sig'][:64], 16) sig_s = int(signed['sig'][64:], 16) sig = sigencode_der(sig_r, sig_s, generator_secp256k1.order()) txin['signatures'][ii] = to_hexstr(sig) + '41' tx._inputs[i] = txin except UserCancelled: raise except BaseException as e: self.give_error(e, True) else: print_error("Transaction is_complete", tx.is_complete()) tx.raw = tx.serialize()
def __init__(self, H): assert isinstance(H, bytes) if not seclib: try: Hpoint = ser_to_point(H) except: raise ValueError("H could not be parsed") HGpoint = Hpoint + ecdsa.SECP256k1.generator if HGpoint == ecdsa.ellipticcurve.INFINITY: # this happens if H = -G raise InsecureHPoint(-1) self._ecdsa_H = Hpoint self._ecdsa_HG = HGpoint self.H = point_to_ser(Hpoint, comp=False) self.HG = point_to_ser(HGpoint, comp=False) else: ctx = seclib.ctx H_buf = create_string_buffer(64) res = seclib.secp256k1_ec_pubkey_parse(ctx, H_buf, H, c_size_t(len(H))) if not res: raise ValueError( 'H could not be parsed by the secp256k1 library') self._seclib_H = H_buf.raw G = point_to_ser(ecdsa.SECP256k1.generator, comp=False) G_buf = create_string_buffer(64) res = seclib.secp256k1_ec_pubkey_parse(ctx, G_buf, G, c_size_t(len(G))) assert res, "G point should always deserialize without issue" HG_buf = create_string_buffer(64) publist = (c_void_p * 2)(*(cast(x, c_void_p) for x in (H_buf, G_buf))) res = seclib.secp256k1_ec_pubkey_combine(ctx, HG_buf, publist, 2) if res != 1: # this happens if H = -G raise InsecureHPoint(-1) self._seclib_HG = HG_buf.raw # now serialize H and HG as uncompressed bytes serpoint = create_string_buffer(65) sersize = c_size_t(65) res = seclib.secp256k1_ec_pubkey_serialize( ctx, serpoint, byref(sersize), H_buf, secp256k1.SECP256K1_EC_UNCOMPRESSED) assert res == 1 assert sersize.value == 65 self.H = serpoint.raw res = seclib.secp256k1_ec_pubkey_serialize( ctx, serpoint, byref(sersize), HG_buf, secp256k1.SECP256K1_EC_UNCOMPRESSED) assert res == 1 assert sersize.value == 65 self.HG = serpoint.raw
def restore_from_privkey(self, secret_string): "restore key pair from private key expressed in a hex form" self.private_key = string_to_number(bytes.fromhex(secret_string)) self.eck = EC_KEY(bytes.fromhex(secret_string)) self.public_key = point_to_ser(self.private_key*self.G, True)
def generate_key_pair(self): """ generate encryption/decryption pair """ self.private_key = ecdsa.util.randrange( self._r ) self.eck = EC_KEY(number_to_string(self.private_key, self._r)) self.public_key = point_to_ser(self.private_key*self.G, True)