def sign_message(self, sequence, message, password): sig = None try: inputPath = self.get_derivation() + "/%d/%d" % sequence inputHash = Hash(msg_magic(message)).encode('hex') hasharray = [] hasharray.append({'hash': inputHash, 'keypath': inputPath}) hasharray = json.dumps(hasharray) msg = '{"sign":{"meta":"sign message", "data":%s}}' % (hasharray) 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 ...\r\n\r\n" \ "To continue, touch the Digital Bitbox's blinking light for 3 seconds.\r\n\r\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.clear_dialog() 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 = chr(27 + int(reply['sign'][0]['recid'], 16) + 4) + reply['sign'][0]['sig'].decode('hex') h = Hash(msg_magic(message)) pk, compressed = pubkey_from_signature(sig, h) 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 = chr(27 + i + 4) + reply['sign'][0]['sig'].decode('hex') try: addr = public_key_to_p2pkh( reply['sign'][0]['pubkey'].decode('hex')) 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_tx_signature(self, signature, transaction, verification_key): "It verifies the transaction signatures" txin = list( filter(lambda x: verification_key in x['pubkeys'], transaction.inputs())) if txin: tx_num = transaction.inputs().index(txin[0]) pre_hash = Hash(bfh(transaction.serialize_preimage(tx_num))) order = generator_secp256k1.order() r, s = ecdsa.util.sigdecode_der(bfh(signature.decode()[:-2]), order) sig_string = ecdsa.util.sigencode_string(r, s, order) compressed = len(verification_key) <= 66 for recid in range(0, 4): try: pubk = MyVerifyingKey.from_signature(sig_string, recid, pre_hash, curve=SECP256k1) pubkey = bh2u(point_to_ser(pubk.pubkey.point, compressed)) if verification_key == pubkey: return True except: continue else: return False
def verify_message_with_address(address: str, sig65: bytes, message: bytes, *, net=None): from electroncash.bitcoin import pubkey_to_address assert_bytes(sig65, message) if net is None: net = constants.net try: h = Hash(msg_magic(message)) public_key, compressed = ECPubkey.from_signature65(sig65, h) # check public key using the address pubkey_hex = public_key.get_public_key_hex(compressed) for txin_type in ['p2pkh', 'p2wpkh', 'p2wpkh-p2sh']: addr = pubkey_to_address(txin_type, pubkey_hex, net=net) if address == addr: break else: raise Exception("Bad signature") # check message public_key.verify_message_hash(sig65[1:], h) return True except Exception as e: print_error(f"Verification error: {repr(e)}") return False
def parse_message_signature(self, response, message, pubkey): # Prepend the message for signing as done inside the card!! message = to_bytes(message, 'utf8') hash = Hash(msg_magic(message)) coordx = pubkey.get_public_key_bytes() response = bytearray(response) recid = -1 for id in range(4): compsig = self.parse_to_compact_sig(response, id, compressed=True) # remove header byte compsig2 = compsig[1:] try: pk = ECPubkey.from_sig_string(compsig2, id, hash) pkbytes = pk.get_public_key_bytes(compressed=True) except InvalidECPointException: continue if coordx == pkbytes: recid = id break if recid == -1: raise ValueError("Unable to recover public key from signature") return compsig
def verify_tx_signature(self, signature, transaction, verification_key, utxo): '''Verify the signature for a specific utxo ("prevout_hash:n") given a transaction and verification key.''' txin = list( filter( lambda x: (verification_key in x['pubkeys'] and utxo == "{}:{}".format( x['tx_hash'], x['tx_pos'])), transaction.inputs())) if txin: tx_num = transaction.inputs().index(txin[0]) pre_hash = Hash(bfh(transaction.serialize_preimage(tx_num))) order = generator_secp256k1.order() r, s = ecdsa.util.sigdecode_der(bfh(signature.decode()[:-2]), order) sig_string = ecdsa.util.sigencode_string(r, s, order) compressed = len(verification_key) <= 66 for recid in range(0, 4): try: pubk = MyVerifyingKey.from_signature(sig_string, recid, pre_hash, curve=SECP256k1) pubkey = bh2u(point_to_ser(pubk.pubkey.point, compressed)) if verification_key == pubkey: return True except: continue else: return False
def verify_signature(self, signature, message, verification_key): "This method verifies signature of message" pk, compressed = pubkey_from_signature(signature, Hash(msg_magic(message))) address_from_signature = public_key_to_p2pkh( point_to_ser(pk.pubkey.point, compressed)) address_from_vk = self.address(verification_key) return address_from_signature == address_from_vk
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 verify_message_for_address(self, sig65: bytes, message: bytes) -> None: assert_bytes(message) h = Hash(msg_magic(message)) public_key, compressed = self.from_signature65(sig65, h) # check public key if public_key != self: raise Exception("Bad signature") # check message self.verify_message_hash(sig65[1:], h)
def checkd_data_sig(self,sig,pre,pk): sec, compressed = self.keypair.get(pk) pre_hash = Hash(pre) pkey = regenerate_key(sec) secexp = pkey.secret private_key = MySigningKey.from_secret_exponent(secexp, curve=ecdsa.SECP256k1) public_key = private_key.get_verifying_key() print("Data signature ok:") print(public_key.verify_digest(sig[:-1], pre_hash, sigdecode=ecdsa.util.sigdecode_der))
def test_001_sufficient_funds(self): pubkey_1, pubkey_2, pubkey_3 = self.public_keys[0:3] address_1, address_2, address_3 = self.addresses[0:3] address_x = "111111111111111111111111111" inputs = { pubkey_1:[ fake_hash(address_1, 1000) + ":0", fake_hash(address_1, 100) + ":0" ], pubkey_2:[ fake_hash(address_2, 1000) + ":0"] } self.assertTrue(self.coin.check_inputs_for_sufficient_funds(inputs, 2022)) self.assertFalse(self.coin.check_inputs_for_sufficient_funds(inputs, 20022)) bad_hash_input = {Hash("pubkey_1").hex():["a1h4"]} self.assertIsNone(self.coin.check_inputs_for_sufficient_funds(bad_hash_input, 2222)) bad_address_input = {Hash("pubkey_111").hex():["a111h1"]} self.assertIsNone(self.coin.check_inputs_for_sufficient_funds(bad_address_input, 2222))
def get_transaction_signature(self, tx, sk, vk): txin = list(filter(lambda x: vk in x['pubkeys'], tx.inputs())) if txin: tx_num = tx.inputs().index(txin[0]) pre_hash = Hash(bfh(tx.serialize_preimage(tx_num))) private_key = MySigningKey.from_secret_exponent(sk.secret, curve = SECP256k1) public_key = private_key.get_verifying_key() sig = private_key.sign_digest_deterministic(pre_hash, hashfunc=hashlib.sha256, sigencode = ecdsa.util.sigencode_der) assert public_key.verify_digest(sig, pre_hash, sigdecode = ecdsa.util.sigdecode_der) result = bh2u(sig) + int_to_hex(tx.nHashType() & 255, 1) return result.encode('utf-8') return b''
def hid_send_encrypt(self, msg): reply = "" try: secret = Hash(self.password) msg = EncodeAES(secret, msg) reply = self.hid_send_plain(msg) if 'ciphertext' in reply: reply = DecodeAES(secret, ''.join(reply["ciphertext"])) reply = json.loads(reply) if 'error' in reply: self.password = None except Exception as e: print_error('Exception caught ' + str(e)) return reply
def get_transaction_signature(transaction, inputs, secret_keys): "get transaction signature" signatures = {} for txin in transaction.inputs(): pubkey = txin['pubkeys'][0] if pubkey in inputs: tx_num = transaction.inputs().index(txin) pre_hash = Hash(bfh(transaction.serialize_preimage(tx_num))) private_key = MySigningKey.from_secret_exponent(secret_keys[pubkey].secret, curve=SECP256k1) public_key = private_key.get_verifying_key() sig = private_key.sign_digest_deterministic(pre_hash, hashfunc=hashlib.sha256, sigencode=ecdsa.util.sigencode_der) assert public_key.verify_digest(sig, pre_hash, sigdecode=ecdsa.util.sigdecode_der) signatures[txin['tx_hash'] + ":" + str(txin['tx_pos'])] = (bh2u(sig) + int_to_hex(transaction.nHashType() & 255, 1)).encode('utf-8') return signatures
def verify_tx_signature(signature, transaction, verification_key, utxo): '''Verify the signature for a specific utxo ("prevout_hash:n") given a transaction and verification key. Ensures that the signature is valid AND canonically encoded, so it will be accepted by network. ''' tx_num = None for n, x in enumerate(transaction.inputs()): if (verification_key in x['pubkeys'] and utxo == "{}:{}".format(x['tx_hash'], x['tx_pos'])): tx_num = n break else: # verification_key / utxo combo not found in tx inputs, bail return False # calculate sighash digest (implicitly this is for sighash 0x41) pre_hash = Hash(bfh(transaction.serialize_preimage(tx_num))) order = generator_secp256k1.order() try: sigbytes = bfh(signature.decode()) except ValueError: # not properly hex encoded or UnicodeDecodeError (garbage data) return False if not sigbytes or sigbytes[-1] != 0x41: return False DERsig = sigbytes[: -1] # lop off the sighash byte for the DER check below try: # ensure DER encoding is canonical, and extract r,s if OK r, s = CoinUtils.IsValidDERSignatureEncoding_With_Extract(DERsig) except AssertionError: return False if (s << 1) > order: # high S values are rejected by BCH network return False try: pubkey_point = ser_to_point(bfh(verification_key)) except: # ser_to_point will fail if pubkey is off-curve, infinity, or garbage. return False vk = MyVerifyingKey.from_public_point(pubkey_point, curve=SECP256k1) try: return vk.verify_digest(DERsig, pre_hash, sigdecode=ecdsa.util.sigdecode_der) except: # verify_digest returns True on success, otherwise raises return False
def sign_message(self, message: bytes, is_compressed: bool) -> bytes: def bruteforce_recid(sig_string): for recid in range(4): sig65 = construct_sig65(sig_string, recid, is_compressed) try: self.verify_message_for_address(sig65, message) return sig65, recid except Exception as e: continue else: raise Exception("error: cannot sign message. no recid fits..") message = to_bytes(message, 'utf8') msg_hash = Hash(msg_magic(message)) sig_string = self.sign(msg_hash, sigencode=sig_string_from_r_and_s, sigdecode=get_r_and_s_from_sig_string) sig65, recid = bruteforce_recid(sig_string) return sig65
def verify_signature(self, signature, message, verification_key): "This method verifies signature of message" pk, compressed = pubkey_from_signature(signature, Hash(msg_magic(message))) pubkey = point_to_ser(pk.pubkey.point, compressed).hex() return pubkey == verification_key
def sign_transaction(self, tx, password): self.print_error('sign_transaction(): tx: ' + str(tx)) #debugSatochip client = self.get_client() # outputs txOutputs = ''.join(tx.serialize_output(o) for o in tx.outputs()) hashOutputs = bh2u(Hash(bfh(txOutputs))) txOutputs = var_int(len(tx.outputs())) + txOutputs self.print_error('sign_transaction(): hashOutputs= ', hashOutputs) #debugSatochip self.print_error('sign_transaction(): outputs= ', txOutputs) #debugSatochip # Fetch inputs of the transaction to sign derivations = self.get_tx_derivations(tx) for i, txin in enumerate(tx.inputs()): self.print_error('sign_transaction(): input =', i) #debugSatochip self.print_error('sign_transaction(): input[type]:', txin['type']) #debugSatochip if txin['type'] == 'coinbase': self.give_error( "Coinbase not supported") # should never happen if txin['type'] in ['p2sh']: p2shTransaction = True pubkeys, x_pubkeys = tx.get_sorted_pubkeys(txin) for j, x_pubkey in enumerate(x_pubkeys): self.print_error('sign_transaction(): forforloop: j=', j) #debugSatochip if tx.is_txin_complete(txin): break if x_pubkey in derivations: signingPos = j s = derivations.get(x_pubkey) address_path = "%s/%d/%d" % (self.get_derivation()[2:], s[0], s[1]) # get corresponing extended key (depth, bytepath) = bip32path2bytes(address_path) (key, chaincode ) = client.cc.card_bip32_get_extendedkey(bytepath) # parse tx pre_tx_hex = tx.serialize_preimage(i) pre_tx = bytes.fromhex( pre_tx_hex) # hex representation => converted to bytes pre_hash = Hash(bfh(pre_tx_hex)) pre_hash_hex = pre_hash.hex() self.print_error('sign_transaction(): pre_tx_hex=', pre_tx_hex) #debugSatochip self.print_error('sign_transaction(): pre_hash=', pre_hash_hex) #debugSatochip (response, sw1, sw2) = client.cc.card_parse_transaction( pre_tx, True ) # use 'True' since BCH use BIP143 as in Segwit... #print_error('[satochip] sign_transaction(): response= '+str(response)) #debugSatochip (tx_hash, needs_2fa ) = client.parser.parse_parse_transaction(response) # tx_hash should be equal to pre_hash_hex self.print_error('sign_transaction(): tx_hash=', bytes(tx_hash).hex()) #debugSatochip #todo: assert() # sign tx keynbr = 0xFF #for extended key if needs_2fa: # format & encrypt msg import json coin_type = 145 #see https://github.com/satoshilabs/slips/blob/master/slip-0044.md test_net = networks.net.TESTNET msg = { 'action': "sign_tx", 'tx': pre_tx_hex, 'ct': coin_type, 'sw': True, 'tn': test_net, 'txo': txOutputs, 'ty': txin['type'] } msg = json.dumps(msg) (id_2FA, msg_out) = client.cc.card_crypt_transaction_2FA( msg, True) d = {} d['msg_encrypt'] = msg_out d['id_2FA'] = id_2FA # self.print_error("encrypted message: "+msg_out) self.print_error("id_2FA:", id_2FA) #do challenge-response with 2FA device... client.handler.show_message( '2FA request sent! Approve or reject request on your second device.' ) run_hook('do_challenge_response', d) # decrypt and parse reply to extract challenge response try: reply_encrypt = None # init it in case of exc below reply_encrypt = d['reply_encrypt'] except Exception as e: # Note: give_error here will raise again.. :/ self.give_error( "No response received from 2FA.\nPlease ensure that the Satochip-2FA plugin is enabled in Tools>Optional Features", True) break if reply_encrypt is None: #todo: abort tx break reply_decrypt = client.cc.card_crypt_transaction_2FA( reply_encrypt, False) self.print_error("challenge:response=", reply_decrypt) reply_decrypt = reply_decrypt.split(":") rep_pre_hash_hex = reply_decrypt[0][0:64] if rep_pre_hash_hex != pre_hash_hex: #todo: abort tx or retry? self.print_error("Abort transaction: tx mismatch:", rep_pre_hash_hex, "!=", pre_hash_hex) break chalresponse = reply_decrypt[1] if chalresponse == "00" * 20: #todo: abort tx? self.print_error( "Abort transaction: rejected by 2FA!") break chalresponse = list(bytes.fromhex(chalresponse)) else: chalresponse = None (tx_sig, sw1, sw2) = client.cc.card_sign_transaction( keynbr, tx_hash, chalresponse) self.print_error('sign_transaction(): sig=', bytes(tx_sig).hex()) #debugSatochip #todo: check sw1sw2 for error (0x9c0b if wrong challenge-response) # enforce low-S signature (BIP 62) tx_sig = bytearray(tx_sig) r, s = get_r_and_s_from_der_sig(tx_sig) if s > CURVE_ORDER // 2: s = CURVE_ORDER - s tx_sig = der_sig_from_r_and_s(r, s) #update tx with signature tx_sig = tx_sig.hex() + '41' #tx.add_signature_to_txin(i,j,tx_sig) txin['signatures'][j] = tx_sig break else: self.give_error("No matching x_key for sign_transaction" ) # should never happen self.print_error("is_complete", tx.is_complete()) tx.raw = tx.serialize() return
def verify_signature(self, sig, message, vk): pk, compressed = pubkey_from_signature(sig,Hash(msg_magic(message))) address_from_signature = public_key_to_p2pkh(point_to_ser(pk.pubkey.point,compressed)) address_from_vk = self.address(vk) return address_from_signature == address_from_signature
def sign_message(self, sequence, message, password, sigtype=SignatureType.BITCOIN): if sigtype == SignatureType.ECASH: raise RuntimeError( _('eCash message signing is not available for {}').format( self.device)) 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 sign_transaction(self, tx, password): if tx.is_complete(): return try: p2shTransaction = False 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'] in ['p2sh']: p2shTransaction = True 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( tx.serialize_preimage(i).decode('hex')) hasharray_i = { 'hash': inputHash.encode('hex'), 'keypath': inputPath } hasharray.append(hasharray_i) inputhasharray.append(inputHash) break else: self.give_error("No matching x_key for sign_transaction" ) # should never happen # Sanity check if p2shTransaction: for txinput in tx.inputs(): if txinput['type'] != 'p2sh': self.give_error( "P2SH / regular input mixed in same transaction not supported" ) # should never happen # Build pubkeyarray from outputs (unused because echo for smart verification not implemented) if not p2shTransaction: for _type, address, amount in tx.outputs(): assert _type == TYPE_ADDRESS info = tx.output_info.get(address) if info is not None: index, xpubs, m = 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) # 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": {"meta":"%s", "data":%s, "checkpub":%s} }' % \ (Hash(tx.serialize()).encode('hex'), json.dumps(hashes), json.dumps(pubkeyarray)) 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 steps > 1: self.handler.show_message(_("Signing large transaction. Please be patient ...\r\n\r\n" \ "To continue, touch the Digital Bitbox's blinking light for 3 seconds. " \ "(Touch " + str(step + 1) + " of " + str(int(steps)) + ")\r\n\r\n" \ "To cancel, briefly touch the blinking light or wait for the timeout.\r\n\r\n")) else: self.handler.show_message(_("Signing transaction ...\r\n\r\n" \ "To continue, touch the Digital Bitbox's blinking light for 3 seconds.\r\n\r\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.clear_dialog() if 'error' in reply: 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 = 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 = signed['sig'].decode('hex') h = inputhasharray[i] pk = MyVerifyingKey.from_signature(s, recid, h, curve=SECP256k1) pk = point_to_ser(pk.pubkey.point, True).encode('hex') 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] = sig.encode('hex') + int_to_hex( Transaction.nHashType() & 255, 1) tx._inputs[i] = txin except BaseException as e: self.give_error(e, True) else: print_error("Transaction is_complete", tx.is_complete()) tx.raw = tx.serialize()
def hash(text): ''' Returns sha256(sha256(text)) as bytes. text may be bytes or str. ''' return Hash(text) # bitcoin.Hash is sha256(sha256(x))
def derive_keys(x): h = Hash(x) h = hashlib.sha512(h).digest() return (h[:32],h[32:])
def sign_transaction(self, tx, password): 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(): if not _type == TYPE_ADDRESS: self.give_error(_("Only address outputs are supported by {}").format(self.name)) 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 fake_hash(address, value): return Hash("{}{}".format(address, value)).hex()