async def wallet(request: Request) -> Wallet: provider: Provider = request.scope['provider'] pub_key: str = request.path_params['pub_key'] # noinspection PyShadowingNames wallet = await provider.get_wallet(pub_key) message = '|'.join([ request.method, request.url.path if not request.url.query else f'{request.url.path}?{request.url.query}', (await request.body()).decode('utf-8', errors='replace') or '{}', ]).encode('utf-8') message_hash = double_sha256(message) key = Key(pub_key) x_signature = request.headers.get('x-signature', '') signature = base64.b16decode(x_signature, casefold=True) if not signature: raise Exception('Signature must exist') if not verify(message_hash, signature, key): raise Exception('Verify signature failure') yield wallet
def SignRemoteHTLCToUs(self, request, context): """BOLT #3 - HTLC Outputs, phase 1 Sign an htlc-success tx on a remote HTLC output offered to us, assuming we know the preimage, at force-close time """ node_id = request.node_id.data node = self.nodes.get(node_id) logger.debug("SignRemoteHTLCToUs node:%s" % node_id.hex()) channel_nonce = request.channel_nonce.data channel = node.channels.get(channel_nonce) remote_per_commit_point = request.remote_per_commit_point.data tx = request.tx amount = tx.input_descs[0].prev_output.value_sat redeemscript = tx.input_descs[0].redeem_script reply = remotesigner_pb2.SignatureReply() htlc_key = Key( import_key=channel.basepoints.keys.htlc_basepoint_secret, is_private=True) htlc_basepoint = htlc_key.public_compressed_byte local_priv_key = derive_priv_key( remote_per_commit_point, htlc_basepoint, channel.basepoints.keys.htlc_basepoint_secret) local_key = Key(import_key=local_priv_key, is_private=True) keys = [local_key] tx = Transaction.import_raw(tx.raw_tx_bytes, self.network) tx.inputs[0].script_type = "p2wsh" tx.inputs[0].witness_type = 'segwit' tx.inputs[0].redeemscript = redeemscript tx.inputs[0].value = amount tx.witness_type = 'segwit' tx.sign(keys=keys, tid=0) logger.debug("remote_per_commit_point %s" % remote_per_commit_point.hex()) logger.debug("channel.basepoints.funding_pk %s" % channel.basepoints.funding_pk.hex()) reply.signature.data = tx.inputs[0].signatures[0].as_der_encoded( ) + b'\x01' return reply
def SignRemoteCommitmentTx(self, request, context): """BOLT #3 - Commitment Transaction, phase 1 Sign the remote commitment tx, at commitment time """ node_id = request.node_id.data node = self.nodes.get(node_id) logger.debug("SignRemoteCommitmentTx node:%s" % node_id.hex()) channel_nonce = request.channel_nonce.data channel = node.channels.get(channel_nonce) tx = request.tx amount = tx.input_descs[0].prev_output.value_sat reply = remotesigner_pb2.SignatureReply() local_key = Key(import_key=channel.funding_privkey, is_private=True) remote_pub = channel.remote_basepoints.funding_pubkey remote_pubkey = Key( import_key=channel.remote_basepoints.funding_pubkey) pubs = sorted([remote_pub, local_key.public_compressed_byte]) if remote_pub == pubs[0]: keys = [remote_pubkey, local_key] else: keys = [local_key, remote_pubkey] tx = Transaction.import_raw(tx.raw_tx_bytes, self.network) tx.inputs[0].script_type = "p2wsh" tx.inputs[0].witness_type = 'segwit' tx.inputs[0].sigs_required = 2 tx.inputs[0].value = amount tx.witness_type = 'segwit' tx.sign(keys=keys, tid=0) logger.debug("channel.basepoints.funding_pk %s" % channel.basepoints.funding_pk.hex()) reply.signature.data = tx.inputs[0].signatures[0].as_der_encoded( ) + b'\x01' return reply
def SignDelayedPaymentToUs(self, request, context): """BOLT #5 - Unilateral Close Handling, phase 1 Sign a delayed to-local output - either from the commitment tx or from an HTLC, at force-close time """ node_id = request.node_id.data node = self.nodes.get(node_id) logger.debug("SignDelayedPaymentToUs node:%s" % node_id.hex()) channel_nonce = request.channel_nonce.data channel = node.channels.get(channel_nonce) n = request.n tx = request.tx amount = tx.input_descs[0].prev_output.value_sat redeemscript = tx.input_descs[0].redeem_script reply = remotesigner_pb2.SignatureReply() delayed_payment_basepoint = channel.basepoints.delayed_payment element = shachain_derive(channel.shaelement, START_INDEX - n) per_commitment_point_prv = coincurve.PrivateKey( secret=bytes(element.secret)) local_per_commit_point = per_commitment_point_prv.public_key.format() local_priv_key = derive_priv_key( local_per_commit_point, delayed_payment_basepoint, channel.basepoints.keys.delayed_payment_basepoint_secret) local_key = Key(import_key=local_priv_key, is_private=True) keys = [local_key] tx = Transaction.import_raw(tx.raw_tx_bytes, self.network) tx.inputs[0].script_type = "p2wsh" tx.inputs[0].witness_type = 'segwit' tx.inputs[0].redeemscript = redeemscript tx.inputs[0].value = amount tx.witness_type = 'segwit' tx.sign(keys=keys, tid=0) reply.signature.data = tx.inputs[0].signatures[0].as_der_encoded( ) + b'\x01' return reply
def SignLocalHTLCTx(self, request, context): """BOLT #3 - HTLC Outputs, phase 1 Sign an htlc-success tx spending a local HTLC output, assuming we know the preimage, at force-close time """ node_id = request.node_id.data node = self.nodes.get(node_id) logger.debug("SignLocalHTLCTx node:%s" % node_id.hex()) channel_nonce = request.channel_nonce.data channel = node.channels.get(channel_nonce) n = request.n tx = request.tx amount = tx.input_descs[0].prev_output.value_sat redeemscript = tx.input_descs[0].redeem_script reply = remotesigner_pb2.SignatureReply() htlc_basepoint = channel.basepoints.htlc element = shachain_derive(channel.shaelement, START_INDEX - n) per_commitment_point_prv = coincurve.PrivateKey( secret=bytes(element.secret)) local_per_commit_point = per_commitment_point_prv.public_key.format() local_priv_key = derive_priv_key( local_per_commit_point, htlc_basepoint, channel.basepoints.keys.htlc_basepoint_secret) local_key = Key(import_key=local_priv_key, is_private=True) keys = [local_key] tx = Transaction.import_raw(tx.raw_tx_bytes, self.network) tx.inputs[0].script_type = "p2wsh" tx.inputs[0].witness_type = 'segwit' tx.inputs[0].redeemscript = redeemscript tx.inputs[0].value = amount tx.witness_type = 'segwit' tx.sign(keys=keys, tid=0) reply.signature.data = tx.inputs[0].signatures[0].as_der_encoded( ) + b'\x01' return reply
def serialize_multisig_redeemscript(key_list, n_required=None, compressed=True): """ Create a multisig redeemscript used in a p2sh. Contains the number of signatures, followed by the list of public keys and the OP-code for the number of signatures required. :param key_list: List of public keys :type key_list: Key, list :param n_required: Number of required signatures :type n_required: int :param compressed: Use compressed public keys? :type compressed: bool :return bytes: A multisig redeemscript """ if not key_list: return b'' if not isinstance(key_list, list): raise TransactionError("Argument public_key_list must be of type list") public_key_list = [] for k in key_list: if isinstance(k, Key): if compressed: public_key_list.append(k.public_byte) else: public_key_list.append(k.public_uncompressed_byte) elif len(k) == 65 and k[0:1] == b'\x04' or len(k) == 33 and k[0:1] in [ b'\x02', b'\x03' ]: public_key_list.append(k) else: try: kobj = Key(k) if compressed: public_key_list.append(kobj.public_byte) else: public_key_list.append(kobj.public_uncompressed_byte) except: raise TransactionError( "Unknown key %s, please specify Key object, public or private key string" ) return _serialize_multisig_redeemscript(public_key_list, n_required)
def SignPenaltyToUs(self, request, context): """BOLT #5 - Unilateral Close Handling, phase 1 Sign a penalty tx to us - either sweeping the to-local commitment tx output or sweeping an HTLC tx output, after the remote broadcast a revoked commitment transaction. """ node_id = request.node_id.data node = self.nodes.get(node_id) logger.debug("SignPenaltyToUs node:%s" % node_id.hex()) channel_nonce = request.channel_nonce.data channel = node.channels.get(channel_nonce) revocation_secret = request.revocation_secret.data tx = request.tx amount = tx.input_descs[0].prev_output.value_sat redeemscript = tx.input_descs[0].redeem_script index = request.input revocationprivkey = derive_blinded_privkey( revocation_secret, channel.basepoints.keys.revocation_basepoint_secret) local_key = Key(import_key=revocationprivkey, is_private=True) keys = [local_key] tx = Transaction.import_raw(tx.raw_tx_bytes, self.network) tx.inputs[index].script_type = "p2wsh" tx.inputs[index].witness_type = 'segwit' tx.inputs[index].redeemscript = redeemscript tx.inputs[index].value = amount tx.witness_type = 'segwit' tx.sign(keys=keys, tid=index) reply = remotesigner_pb2.SignatureReply() reply.signature.data = tx.inputs[index].signatures[0].as_der_encoded( ) + b'\x01' return reply
def create_transaction(self, wallet_address_list, private_input_address_list, output_address, change_address, send_amount, fee_per_kb, network): try: print( 'Create and sign Transaction with Multiple INPUTS using keys from Wallet class ' ) charge_back_amount = 0 print('Dummy transaction with Outputs ') transaction_outputs = [ Output(send_amount, address=output_address, network=network), Output(charge_back_amount, address=change_address, network=network) ] print('transaction_outputs', transaction_outputs) print('created dummy txn for fee calc') t = Transaction(outputs=transaction_outputs, network=network, fee_per_kb=fee_per_kb) print('Fee with o/p ', t.calculate_fee()) fee = t.calculate_fee() charge_back_amount = send_amount - fee ''' if charge_back_amount < 0: raise ServiceException('Insufficient fund to continue transaction') ''' print('created real txn for fee calc') transaction_outputs = [ Output(send_amount, address=output_address, network=network), Output(charge_back_amount, address=change_address, network=network) ] print('transaction_outputs', transaction_outputs) transaction_inputs = [] for input in wallet_address_list: transaction_inputs.append( (input['txid'], int(input['vout']), self.json_get(private_input_address_list, input['address']))) print('transaction_inputs', transaction_inputs) t.fee = t.calculate_fee() print('Fee with ip/ op ', t.fee) for ti in transaction_inputs: ki = Key(ti[2], network=network) t.add_input(prev_hash=ti[0], output_n=ti[1], keys=ki.public()) icount = 0 for ti in transaction_inputs: ki = Key(ti[2], network=network) t.sign(ki.private_byte, icount) icount += 1 print('\nRaw Signed Transaction %s' % binascii.hexlify(t.raw())) print('\nVerified %s' % t.verify()) print('------------info-----------') t.info() print('------------end-----------') except Exception as e: raise e return t
# # BitcoinLib - Python Cryptocurrency Library # # EXAMPLES - Key and HDKey Class # # © 2017 September - 1200 Web Development <http://1200wd.com/> # from pprint import pprint from bitcoinlib.keys import Address, HDKey, Key, deserialize_address # Key Class Examples print("\n=== Generate random key ===") k = Key() k.info() print("\n=== Import Public key ===") K = Key('025c0de3b9c8ab18dd04e3511243ec2952002dbfadc864b9628910169d9b9b00ec') K.info() print("\n=== Import Private key as decimal ===") pk = 45552833878247474734848656701264879218668934469350493760914973828870088122784 k = Key(import_key=pk, network='testnet') k.info() print("\n=== Import Private key as byte ===") pk = b':\xbaAb\xc7%\x1c\x89\x12\x07\xb7G\x84\x05Q\xa7\x199\xb0\xde\x08\x1f\x85\xc4\xe4L\xf7\xc1>A\xda\xa6\x01' k = Key(pk) k.info()
def SignFundingTx(self, request, context): """BOLT #3 - Funding Transaction Sign the funding transaction TODO this must be done after we know the remote signature on the commitment tx - need an API call that provides this to the signer. """ node_id = request.node_id.data node = self.nodes.get(node_id) logger.debug("SignFundingTx node:%s" % node_id) xpub = node.bip32_key.get_xpriv_from_path('m/0/0') onchain_bip32_key = BIP32.from_xpriv(xpub) tx = request.tx tx = Transaction.import_raw(tx.raw_tx_bytes, self.network) reply = remotesigner_pb2.SignFundingTxReply() witnesses = remotesigner_pb2.Witness() local_prv = None local_key = None logger.debug(request) logger.debug(request.tx.raw_tx_bytes.hex()) for i, input_desc in enumerate(request.tx.input_descs): key_index = input_desc.key_loc.key_index spend_type = input_desc.spend_type amount = input_desc.prev_output.value_sat channel_nonce = input_desc.close_info.channel_nonce.data commitment_point = input_desc.close_info.commitment_point.data channel = node.channels.get(channel_nonce) if key_index != 0: local_prv = onchain_bip32_key.get_privkey_from_path("m/%d" % key_index) local_key = Key(import_key=local_prv, is_private=True) elif channel: if commitment_point: local_priv_key = derive_priv_key( commitment_point, payment_basepoint, channel.basepoints.keys.payment_basepoint_secret) local_key = Key(import_key=local_priv_key, is_private=True) else: local_key = Key(import_key=channel.basepoints.keys. payment_basepoint_secret, is_private=True) else: witnesses.signature.data = b'' witnesses.pubkey.data = b'' reply.witnesses.extend([witnesses]) continue spend_type = spend_type_to_string(spend_type) tx.inputs[i].script_type = spend_type logger.debug("funding signature: %s" % spend_type) tx.inputs[i].witness_type = "segwit" tx.inputs[i].sigs_required = 1 tx.inputs[i].value = amount if spend_type == 'p2pkh': tx.witness_type = 'legacy' else: tx.witness_type = 'segwit' tx.sign(keys=[local_key], tid=i) witnesses.signature.data = tx.inputs[i].witnesses[0] witnesses.pubkey.data = tx.inputs[i].witnesses[1] logger.debug("funding signature: %s" % tx.inputs[i].signatures[0].as_der_encoded().hex()) logger.debug("funding public key %s" % tx.inputs[i].as_dict()['public_keys']) reply.witnesses.extend([witnesses]) logger.debug(reply) return reply
def sign(self, keys, tid=0, hash_type=SIGHASH_ALL): """ Sign the transaction input with provided private key :param keys: A private key or list of private keys :type keys: HDKey, Key, bytes, list :param tid: Index of transaction input :type tid: int :param hash_type: Specific hash type, default is SIGHASH_ALL :type hash_type: int :return int: Return int with number of signatures added """ n_signs = 0 if not isinstance(keys, list): keys = [keys] if self.inputs[tid].script_type == 'coinbase': raise TransactionError("Can not sign coinbase transactions") tsig = hashlib.sha256(hashlib.sha256(self.raw(tid)).digest()).digest() for key in keys: if isinstance(key, (HDKey, Key)): priv_key = key.private_byte pub_key = key.public_byte else: ko = Key(key) priv_key = ko.private_byte pub_key = ko.public_byte if not priv_key: raise TransactionError( "Please provide a valid private key to sign the transaction. " "%s is not a private key" % priv_key) sk = ecdsa.SigningKey.from_string(priv_key, curve=ecdsa.SECP256k1) while True: sig_der = sk.sign_digest(tsig, sigencode=ecdsa.util.sigencode_der) # Test if signature has low S value, to prevent 'Non-canonical signature: High S Value' errors # TODO: Recalc 's' instead, see: # https://github.com/richardkiss/pycoin/pull/24/files#diff-12d8832e97767321d1f3c40909be8b23 signature = convert_der_sig(sig_der) s = int(signature[64:], 16) if s < ecdsa.SECP256k1.order / 2: break newsig = { 'sig_der': to_bytes(sig_der), 'signature': to_bytes(signature), 'priv_key': priv_key, 'pub_key': pub_key, 'transaction_id': tid } # Check if signature signs known key and is not already in list pub_key_list = [x.public_byte for x in self.inputs[tid].keys] pub_key_list_uncompressed = [ x.public_uncompressed_byte for x in self.inputs[tid].keys ] if pub_key not in pub_key_list: raise TransactionError( "This key does not sign any known key: %s" % pub_key) if pub_key in [x['pub_key'] for x in self.inputs[tid].signatures]: _logger.warning("Key %s already signed" % pub_key) break n_signs += 1 # Insert newsig in correct place in list if self.inputs[tid].signatures: # 1. determine position for newsig according to key list newsig_pos = pub_key_list.index(pub_key) n_total_sigs = len(pub_key_list) # 2. assume signature list is in correct order then determine possible position of newsig sig_start_domain = [''] * n_total_sigs sig_start_domain[newsig_pos] = newsig sig_domains = [] empty_slots = [ i for i, j in enumerate(sig_start_domain) if j == '' ] possible_sig_positions = combinations( empty_slots, len(self.inputs[tid].signatures)) for pp in possible_sig_positions: sig_domain = deepcopy(sig_start_domain) signatures = deepcopy(self.inputs[tid].signatures)[::-1] for sig_pos in pp: sig_domain[sig_pos] = signatures.pop() sig_domains.append(sig_domain) # Verify sig domains for sig_domain in sig_domains: signature_list = [s for s in sig_domain if s != ''] for sig in signature_list: pos = sig_domain.index(sig) if not verify_signature( tsig, sig['signature'], pub_key_list_uncompressed[pos]): sig_domains.remove(sig_domain) break # TODO: Remove all domains with signature on this position if len(sig_domains): self.inputs[tid].signatures = [ s for s in sig_domains[0] if s != '' ] else: raise TransactionError("Invalid signatures found") # TODO: Think about what will happen when 2 or more identical private keys are used else: self.inputs[tid].signatures.append(newsig) if self.inputs[tid].script_type == 'p2pkh': self.inputs[tid].unlocking_script = \ varstr(self.inputs[tid].signatures[0]['sig_der'] + struct.pack('B', hash_type)) + \ varstr(self.inputs[tid].keys[0].public_byte) elif self.inputs[tid].script_type == 'p2sh_multisig': n_tag = self.inputs[tid].redeemscript[0] if not isinstance(n_tag, int): n_tag = struct.unpack('B', n_tag)[0] n_required = n_tag - 80 signatures = [ s['sig_der'] for s in self.inputs[tid].signatures[:n_required] ] if b'' in signatures: raise TransactionError( "Empty signature found in signature list when signing. " "Is DER encoded version of signature defined?") self.inputs[tid].unlocking_script = \ _p2sh_multisig_unlocking_script(signatures, self.inputs[tid].redeemscript, hash_type) else: raise TransactionError( "Script type %s not supported at the moment" % self.inputs[tid].script_type) return n_signs
def __init__(self, amount, address='', public_key_hash=b'', public_key=b'', lock_script=b'', network=DEFAULT_NETWORK): """ Create a new transaction output An transaction outputs locks the specified amount to a public key. Anyone with the private key can unlock this output. The transaction output class contains an amount and the destination which can be provided either as address, public key, public key hash or a locking script. Only one needs to be provided as the they all can be derived from each other, but you can provide as much attributes as you know to improve speed. :param amount: Amount of output in smallest denominator of currency, for example satoshi's for bitcoins :type amount: int :param address: Destination address of output. Leave empty to derive from other attributes you provide. :type address: str :param public_key_hash: Hash of public key :type public_key_hash: bytes, str :param public_key: Destination public key :type public_key: bytes, str :param lock_script: Locking script of output. If not provided a default unlocking script will be provided with a public key hash. :type lock_script: bytes, str :param network: Network, leave empty for default :type network: str """ if not (address or public_key_hash or public_key or lock_script): raise TransactionError( "Please specify address, lock_script, public key or public key hash when " "creating output") self.amount = amount self.lock_script = to_bytes(lock_script) self.public_key_hash = to_bytes(public_key_hash) self.address = address self.public_key = to_bytes(public_key) self.network = Network(network) self.compressed = True self.k = None self.versionbyte = self.network.prefix_address self.script_type = 'p2pkh' if self.public_key: self.k = Key(binascii.hexlify(self.public_key).decode('utf-8'), network=network) self.address = self.k.address() self.compressed = self.k.compressed if self.public_key_hash and not self.address: self.address = pubkeyhash_to_addr(public_key_hash, versionbyte=self.versionbyte) if self.address: address_dict = deserialize_address(self.address) if address_dict['script_type']: self.script_type = address_dict['script_type'] else: raise TransactionError( "Could not determine script type of address %s" % self.address) self.public_key_hash = address_dict['public_key_hash_bytes'] if address_dict[ 'network'] and self.network.network_name != address_dict[ 'network']: raise TransactionError( "Address (%s) is from different network then defined %s" % (address_dict['network'], self.network.network_name)) if not self.public_key_hash and self.k: self.public_key_hash = self.k.hash160() if self.lock_script and not self.public_key_hash: ss = script_deserialize(self.lock_script) self.script_type = ss['script_type'] if self.script_type == 'p2sh': self.versionbyte = self.network.prefix_address_p2sh if self.script_type in ['p2pkh', 'p2sh']: self.public_key_hash = ss['signatures'][0] self.address = pubkeyhash_to_addr(self.public_key_hash, versionbyte=self.versionbyte) else: _logger.warning("Script type %s not supported" % self.script_type) if self.lock_script == b'': if self.script_type == 'p2pkh': self.lock_script = b'\x76\xa9\x14' + self.public_key_hash + b'\x88\xac' elif self.script_type == 'p2sh': self.lock_script = b'\xa9\x14' + self.public_key_hash + b'\x87' else: raise TransactionError( "Unknown output script type %s, please provide own locking script" % self.script_type)
def __init__(self, prev_hash, output_index, keys=None, signatures=None, unlocking_script=b'', script_type='p2pkh', sequence=b'\xff\xff\xff\xff', compressed=True, sigs_required=None, sort=False, network=DEFAULT_NETWORK, tid=0): """ Create a new transaction input :param prev_hash: Transaction hash of the UTXO (previous output) which will be spent. :type prev_hash: bytes, hexstring :param output_index: Output number in previous transaction. :type output_index: bytes, int :param keys: A list of Key objects or public / private key string in various formats. If no list is provided but a bytes or string variable, a list with one item will be created. Optional :type keys: list (bytes, str) :param signatures: Specify optional signatures :type signatures: bytes, str :param unlocking_script: Unlocking script (scriptSig) to prove ownership. Optional :type unlocking_script: bytes, hexstring :param script_type: Type of unlocking script used, i.e. p2pkh or p2sh_multisig. Default is p2pkh :type script_type: str :param sequence: Sequence part of input, you normally do not have to touch this :type sequence: bytes :param compressed: Use compressed or uncompressed public keys. Default is compressed :type compressed: bool :param sigs_required: Number of signatures required for a p2sh_multisig unlocking script :param sigs_required: int :param sort: Sort public keys according to BIP0045 standard. Default is False to avoid unexpected change of key order. :type sort: boolean :param network: Network, leave empty for default :type network: str :param tid: Index of input in transaction. Used by Transaction class. :type tid: int """ self.prev_hash = to_bytes(prev_hash) self.output_index = output_index if isinstance(output_index, numbers.Number): self.output_index_int = output_index self.output_index = struct.pack('>I', output_index) else: self.output_index_int = struct.unpack('I', output_index)[0] self.output_index = output_index if isinstance(keys, (bytes, str)): keys = [keys] self.unlocking_script = to_bytes(unlocking_script) self.unlocking_script_unsigned = b'' self.script_type = script_type self.sequence = to_bytes(sequence) self.compressed = compressed self.network = Network(network) self.tid = tid if keys is None: keys = [] self.keys = [] if not isinstance(keys, list): keys = [keys] if not signatures: signatures = [] if not isinstance(signatures, list): signatures = [signatures] # Sort according to BIP45 standard self.sort = sort if sort: self.keys.sort(key=lambda k: k.public_byte) self.address = '' self.signatures = [] self.redeemscript = b'' if not sigs_required: if script_type == 'p2sh_multisig': raise TransactionError( "Please specify number of signatures required (sigs_required) parameter" ) else: sigs_required = 1 self.sigs_required = sigs_required self.script_type = script_type if prev_hash == b'\0' * 32: self.script_type = 'coinbase' # If unlocking script is specified extract keys, signatures, type from script if unlocking_script: us_dict = script_deserialize(unlocking_script) if not us_dict or us_dict['script_type'] in ['unknown', 'empty']: raise TransactionError( "Could not parse unlocking script (%s)" % binascii.hexlify(unlocking_script)) self.script_type = us_dict['script_type'] self.sigs_required = us_dict['number_of_sigs_n'] self.redeemscript = us_dict['redeemscript'] signatures += us_dict['signatures'] keys += us_dict['keys'] for key in keys: if not isinstance(key, Key): kobj = Key(key, network=network) else: kobj = key if kobj not in self.keys: self.keys.append(kobj) for sig in signatures: sig_der = '' if sig.startswith(b'\x30'): # If signature ends with Hashtype, remove hashtype and continue # TODO: support for other hashtypes if sig.endswith(b'\x01'): _, junk = ecdsa.der.remove_sequence(sig) if junk == b'\x01': sig_der = sig[:-1] else: sig_der = sig try: sig = convert_der_sig(sig[:-1], as_hex=False) except: pass self.signatures.append({ 'sig_der': sig_der, 'signature': to_bytes(sig), 'priv_key': '', 'pub_key': '' }) if self.script_type == 'sig_pubkey': self.script_type = 'p2pkh' if self.script_type == 'p2pkh': if self.keys: self.unlocking_script_unsigned = b'\x76\xa9\x14' + to_bytes( self.keys[0].hash160()) + b'\x88\xac' self.address = self.keys[0].address() elif self.script_type == 'p2sh_multisig': if not self.keys: raise TransactionError( "Please provide keys to append multisig transaction input") if not self.redeemscript: self.redeemscript = serialize_multisig_redeemscript( self.keys, n_required=self.sigs_required, compressed=self.compressed) self.address = pubkeyhash_to_addr( script_to_pubkeyhash(self.redeemscript), versionbyte=self.network.prefix_address_p2sh) self.unlocking_script_unsigned = self.redeemscript
def sign(self, keys, tid=0, hash_type=SIGHASH_ALL): """ Sign the transaction input with provided private key :param keys: A private key or list of private keys :type keys: HDKey, Key, bytes, list :param tid: Index of transaction input :type tid: int :param hash_type: Specific hash type, default is SIGHASH_ALL :type hash_type: int :return int: Return int with number of signatures added """ n_signs = 0 if not isinstance(keys, list): keys = [keys] if self.inputs[tid].script_type == 'coinbase': raise TransactionError("Can not sign coinbase transactions") tsig = hashlib.sha256(hashlib.sha256(self.raw(tid)).digest()).digest() pub_key_list = [x.public_byte for x in self.inputs[tid].keys] pub_key_list_uncompressed = [ x.public_uncompressed_byte for x in self.inputs[tid].keys ] n_total_sigs = len(pub_key_list) sig_domain = [''] * n_total_sigs for key in keys: if isinstance(key, (HDKey, Key)): priv_key = key.private_byte pub_key = key.public_byte else: ko = Key(key, compressed=self.inputs[tid].compressed) priv_key = ko.private_byte pub_key = ko.public_byte if not priv_key: raise TransactionError( "Please provide a valid private key to sign the transaction. " "%s is not a private key" % priv_key) sk = ecdsa.SigningKey.from_string(priv_key, curve=ecdsa.SECP256k1) while True: sig_der = sk.sign_digest(tsig, sigencode=ecdsa.util.sigencode_der) # Test if signature has low S value, to prevent 'Non-canonical signature: High S Value' errors # TODO: Recalc 's' instead, see: # https://github.com/richardkiss/pycoin/pull/24/files#diff-12d8832e97767321d1f3c40909be8b23 signature = convert_der_sig(sig_der) s = int(signature[64:], 16) if s < ecdsa.SECP256k1.order / 2: break newsig = { 'sig_der': to_bytes(sig_der), 'signature': to_bytes(signature), 'priv_key': priv_key, 'pub_key': pub_key, 'transaction_id': tid } # Check if signature signs known key and is not already in list if pub_key not in pub_key_list: raise TransactionError( "This key does not sign any known key: %s" % pub_key) if pub_key in [x['pub_key'] for x in self.inputs[tid].signatures]: _logger.warning("Key %s already signed" % pub_key) break newsig_pos = pub_key_list.index(pub_key) sig_domain[newsig_pos] = newsig n_signs += 1 # Add already known signatures on correct position n_sigs_to_insert = len(self.inputs[tid].signatures) for sig in self.inputs[tid].signatures: free_positions = [i for i, s in enumerate(sig_domain) if s == ''] for pos in free_positions: if verify_signature(tsig, sig['signature'], pub_key_list_uncompressed[pos]): sig_domain[pos] = sig n_sigs_to_insert -= 1 break if n_sigs_to_insert: _logger.info( "Some signatures are replaced with the signatures of the provided keys" ) self.inputs[tid].signatures = [s for s in sig_domain if s != ''] if self.inputs[tid].script_type == 'p2pkh': if len(self.inputs[tid].signatures): self.inputs[tid].unlocking_script = \ varstr(self.inputs[tid].signatures[0]['sig_der'] + struct.pack('B', hash_type)) + \ varstr(self.inputs[tid].keys[0].public_byte) elif self.inputs[tid].script_type == 'p2sh_multisig': n_tag = self.inputs[tid].redeemscript[0] if not isinstance(n_tag, int): n_tag = struct.unpack('B', n_tag)[0] n_required = n_tag - 80 signatures = [ s['sig_der'] for s in self.inputs[tid].signatures[:n_required] ] if b'' in signatures: raise TransactionError( "Empty signature found in signature list when signing. " "Is DER encoded version of signature defined?") self.inputs[tid].unlocking_script = \ _p2sh_multisig_unlocking_script(signatures, self.inputs[tid].redeemscript, hash_type) else: raise TransactionError( "Script type %s not supported at the moment" % self.inputs[tid].script_type) return n_signs - n_sigs_to_insert
from bitcoinlib.keys import HDKey, Key from bitcoinlib.transactions import Input, Output, Transaction, script_deserialize # # Create transactions # print( "\n=== Create and sign transaction with add_input, add_output methods ===") print( "(Based on http://bitcoin.stackexchange.com/questions/3374/how-to-redeem-a-basic-tx/24580)" ) t = Transaction() prev_tx = 'f2b3eb2deb76566e7324307cd47c35eeb88413f971d88519859b1834307ecfec' ki = Key(0x18E14A7B6A307F426A94F8114701E7C8E774E7F9A47E2C2035DB29A206321725, compressed=False) t.add_input(prev_hash=prev_tx, output_n=1, keys=ki.public_hex, compressed=False) t.add_output(99900000, '1runeksijzfVxyrpiyCY2LCBvYsSiFsCm') t.sign(ki.private_byte) pprint(t.as_dict()) print("Raw:", binascii.hexlify(t.raw())) print("Verified %s " % t.verify()) print(t.raw_hex()) print( "\n=== Create and sign transaction with transactions Input and Output objects ===" ) print(
def parse_bytesio(cls, script, message=None, tx_data=None, strict=True, _level=0): """ Parse raw script and return Script object. Extracts script commands, keys, signatures and other data. :param script: Raw script to parse in bytes, BytesIO or hexadecimal string format :type script: BytesIO :param message: Signed message to verify, normally a transaction hash :type message: bytes :param tx_data: Dictionary with extra information needed to verify script. Such as 'redeemscript' for multisignature scripts and 'blockcount' for time locked scripts :type tx_data: dict :param strict: Raise exception when script is malformed, incomplete or not understood. Default is True :type strict: bool :param _level: Internal argument used to avoid recursive depth :type _level: int :return Script: """ commands = [] signatures = [] keys = [] blueprint = [] redeemscript = b'' sigs_required = None # hash_type = SIGHASH_ALL # todo: check hash_type = None if not tx_data: tx_data = {} while script: chb = script.read(1) if not chb: break ch = int.from_bytes(chb, 'big') data = None data_length = 0 if 1 <= ch <= 75: # Data` data_length = ch elif ch == op.op_pushdata1: data_length = int.from_bytes(script.read(1), 'little') elif ch == op.op_pushdata2: data_length = int.from_bytes(script.read(2), 'little') if data_length: data = script.read(data_length) if len(data) != data_length: msg = "Malformed script, not enough data found" if strict: raise ScriptError(msg) else: _logger.warning(msg) if data: data_type = get_data_type(data) commands.append(data) if data_type == 'signature': sig = Signature.parse_bytes(data) signatures.append(sig) hash_type = sig.hash_type blueprint.append('signature') elif data_type == 'signature_object': signatures.append(data) hash_type = data.hash_type blueprint.append('signature') elif data_type == 'key': keys.append(Key(data)) blueprint.append('key') elif data_type == 'key_object': keys.append(data) blueprint.append('key') elif data_type[:4] == 'data': # FIXME: This is arbitrary blueprint.append('data-%d' % len(data)) elif len(commands) >= 2 and commands[-2] == op.op_return: blueprint.append('data-%d' % len(data)) else: # FIXME: Only parse sub-scripts if script is expected try: if _level >= 1: blueprint.append('data-%d' % len(data)) else: s2 = Script.parse_bytes(data, _level=_level + 1) commands.pop() commands += s2.commands blueprint += s2.blueprint keys += s2.keys signatures += s2.signatures redeemscript = s2.redeemscript sigs_required = s2.sigs_required except (ScriptError, IndexError): blueprint.append('data-%d' % len(data)) else: # Other opcode commands.append(ch) blueprint.append(ch) s = cls(commands, message, keys=keys, signatures=signatures, blueprint=blueprint, tx_data=tx_data, hash_type=hash_type) script.seek(0) s._raw = script.read() s.script_types = _get_script_types(blueprint) if 'unknown' in s.script_types: s.script_types = ['unknown'] # Extract extra information from script data for st in s.script_types[:1]: if st == 'multisig': s.redeemscript = s.raw s.sigs_required = s.commands[0] - 80 if s.sigs_required > len(keys): raise ScriptError( "Number of signatures required (%d) is higher then number of keys (%d)" % (s.sigs_required, len(keys))) if len(s.keys) != s.commands[-2] - 80: raise ScriptError("%d keys found but %d keys expected" % (len(s.keys), s.commands[-2] - 80)) elif st in ['p2wpkh', 'p2wsh', 'p2sh'] and len(s.commands) > 1: s.public_hash = s.commands[1] elif st == 'p2pkh' and len(s.commands) > 2: s.public_hash = s.commands[2] s.redeemscript = redeemscript if redeemscript else s.redeemscript if s.redeemscript and 'redeemscript' not in s.tx_data: s.tx_data['redeemscript'] = s.redeemscript s.sigs_required = sigs_required if sigs_required else s.sigs_required return s