def _get_blinding_factors(self, txdetails, wally_tx): utxos = txdetails['used_utxos'] or txdetails['old_used_utxos'] for i, o in enumerate(txdetails['transaction_outputs']): o['wally_index'] = i blinded_outputs = [ o for o in txdetails['transaction_outputs'] if not o['is_fee'] ] for output in blinded_outputs: # TODO: the derivation dance # the following values are in display order, reverse them when converting to bytes output['assetblinder'] = os.urandom(32).hex() output['amountblinder'] = os.urandom(32).hex() endpoints = utxos + blinded_outputs values = [endpoint['satoshi'] for endpoint in endpoints] abfs = b''.join( bytes.fromhex(endpoint['assetblinder'])[::-1] for endpoint in endpoints) vbfs = b''.join( bytes.fromhex(endpoint['amountblinder'])[::-1] for endpoint in endpoints[:-1]) final_vbf = wally.asset_final_vbf(values, len(utxos), abfs, vbfs) blinded_outputs[-1]['amountblinder'] = final_vbf[::-1].hex() for o in blinded_outputs: asset_commitment = wally.asset_generator_from_bytes( bytes.fromhex(o['asset_id'])[::-1], bytes.fromhex(o['assetblinder'])[::-1]) value_commitment = wally.asset_value_commitment( o['satoshi'], bytes.fromhex(o['amountblinder'])[::-1], asset_commitment) o['asset_commitment'] = asset_commitment.hex() o['value_commitment'] = value_commitment.hex() # Write the commitments into the wally tx for signing wally.tx_set_output_asset(wally_tx, o['wally_index'], asset_commitment) wally.tx_set_output_value(wally_tx, o['wally_index'], value_commitment) retval = {} for key in [ 'assetblinders', 'amountblinders', 'asset_commitments', 'value_commitments' ]: # gdk expects to get an empty entry for the fee output too, hence this is over the # transaction outputs, not just the blinded outputs (fee will just have empty # strings) retval[key] = [ o.get(key[:-1], '') for o in txdetails['transaction_outputs'] ] return retval
total_in = sum(values_in) output_values = [ total_in - fee, ] confidential_output_addresses = [ destination_address, ] output_asset_ids = asset_ids_in[:wally.ASSET_TAG_LEN] # end-define_outputs # start-blinding_factors num_inputs = len(values_in) num_outputs = 1 abfs_out = os.urandom(32 * num_outputs) vbfs_out = os.urandom(32 * (num_outputs - 1)) vbfs_out += wally.asset_final_vbf(values_in + output_values, num_inputs, abfs_in + abfs_out, vbfs_in + vbfs_out) # end-blinding_factors # start-decompose_address blinding_pubkeys = [ wally.confidential_addr_to_ec_public_key(confidential_address, address_prefix) for confidential_address in confidential_output_addresses ] non_confidential_addresses = [ wally.confidential_addr_to_addr(confidential_address, address_prefix) for confidential_address in confidential_output_addresses ] script_pubkeys = [
def sign_tx(self, details: Dict) -> Dict: txhex = details['transaction']['transaction'] signing_inputs = details['signing_inputs'] use_ae_protocol = details['use_ae_protocol'] transaction_outputs = details['transaction_outputs'] logging.debug('sign liquid txn with %d inputs and %d outputs', len(signing_inputs), len(transaction_outputs)) def _map_input(input: Dict) -> Dict: is_segwit = input['address_type'] in ['p2wsh', 'csv', 'p2sh-p2wpkh', 'p2wpkh'] mapped = { 'is_witness': is_segwit, 'path': input['user_path'], 'value_commitment': bytes.fromhex(input['commitment']), 'script': bytes.fromhex(input['prevout_script'])} # Additional fields to pass through if using the Anti-Exfil protocol if use_ae_protocol: mapped['ae_host_commitment'] = bytes.fromhex(input['ae_host_commitment']) mapped['ae_host_entropy'] = bytes.fromhex(input['ae_host_entropy']) return mapped # Get inputs and change outputs in form Jade expects jade_inputs = list(map(_map_input, signing_inputs)) change = list(map(self._map_change_output, transaction_outputs)) # Calculate the hash-prevout from the inputs values, abfs, vbfs, input_prevouts = [], [], [], [] for input in signing_inputs: # Get values, abfs and vbfs from inputs (needed to compute the final output vbf) values.append(input['satoshi']) abfs.append(bytes.fromhex(input['assetblinder'])[::-1]) vbfs.append(bytes.fromhex(input['amountblinder'])[::-1]) # Get the input prevout txid and index for hashing later input_prevouts.append(bytes.fromhex(input['txhash'])[::-1]) input_prevouts.append(input['pt_idx'].to_bytes(4, byteorder='little')) hash_prevouts = bytes(wally.sha256d(b''.join(input_prevouts))) # Get the trusted commitments from Jade idx, trusted_commitments = 0, [] blinded_outputs = transaction_outputs[:-1] # Assume last output is fee for output in blinded_outputs[:-1]: # Not the last blinded output as we calculate the vbf for that commitments = self._get_trusted_commitments(idx, output, hash_prevouts, None) trusted_commitments.append(commitments) values.append(output['satoshi']) abfs.append(commitments['abf']) vbfs.append(commitments['vbf']) idx += 1 # Calculate the final vbf values.append(blinded_outputs[idx]['satoshi']) finalAbf = self.jade.get_blinding_factor(hash_prevouts, idx, 'ASSET') abfs.append(finalAbf) final_vbf = bytes(wally.asset_final_vbf(values, len(signing_inputs), b''.join(abfs), b''.join(vbfs))) # Get the final trusted commitments from Jade (with the calculated vbf) commitments = self._get_trusted_commitments(idx, blinded_outputs[idx], hash_prevouts, final_vbf) trusted_commitments.append(commitments) # Add a 'null' commitment for the final (fee) output trusted_commitments.append(None) # Sign! txn = bytes.fromhex(txhex) signatures = self.jade.sign_liquid_tx(self.network, txn, jade_inputs, trusted_commitments, change, use_ae_protocol) assert len(signatures) == len(signing_inputs) result = {} if use_ae_protocol: # If using the Anti-Exfil protocol, the response is a list of # (signer_commitment, signature), so need to unzip the lists signer_commitments, signatures = zip(*signatures) signer_commitments = list(map(bytes.hex, signer_commitments)) result['signer_commitments'] = signer_commitments signatures = list(map(bytes.hex, signatures)) result['signatures'] = signatures # Poke the blinding factors into the results structure self._populate_result(trusted_commitments, result) logging.debug('resolving {}'.format(result)) return json.dumps(result)
def sign_tx(self, required_data): signing_inputs = required_data['signing_inputs'] transaction_outputs = required_data['transaction_outputs'] signing_transactions = required_data['signing_transactions'] signing_address_types = required_data['signing_address_types'] scripts = [] for in_ in signing_inputs: service_xpub = deserialize(in_['service_xpub']) user_xpub = deserialize(self.as_xpub(in_['user_path'][:-1])) pointer = in_['pointer'] redeem_script = proto.MultisigRedeemScriptType( nodes=[user_xpub, service_xpub], address_n=[pointer], signatures=[b'', b''], m=2, csv=in_['subtype']) scripts.append(redeem_script) ins = [] for i, txin in enumerate(signing_inputs): in_ = proto.TxInputType( address_n=txin['user_path'], prev_hash=h2b(txin['txhash']), prev_index=txin['pt_idx'], script_type=proto.InputScriptType.SPENDP2SHWITNESS, multisig=scripts[i], amount=txin['satoshi'], sequence=txin['sequence']) in_.confidential = proto.TxConfidentialAsset( asset=h2b(txin['asset_id'])[::-1], amount_blind=h2b(txin['vbf']), asset_blind=h2b(txin['abf'])) ins.append(in_) values = [] in_vbfs = [] in_abfs = [] for txin in signing_inputs: in_vbfs.append(h2b(txin['vbf'])) in_abfs.append(h2b(txin['abf'])) values.append(txin['satoshi']) out_vbfs = [] out_abfs = [] for i, txout in enumerate(transaction_outputs): if txout['is_fee']: continue out_vbfs.append( os.urandom(32)) # TODO: check if HW is going to generate these out_abfs.append( os.urandom(32)) # TODO: check if HW is going to generate these values.append(txout['satoshi']) abfs = in_abfs + out_abfs vbfs = in_vbfs + out_vbfs[:-1] final_vbf = wally.asset_final_vbf(values, len(signing_inputs), b''.join(abfs), b''.join(vbfs)) out_vbfs[-1] = final_vbf outs = [] for i, txout in enumerate(transaction_outputs): if txout['is_fee']: out = proto.TxOutputType(address='', amount=txout['satoshi']) out.confidential = proto.TxConfidentialAsset( asset=h2b(txout['asset_id'])[::-1]) else: out = proto.TxOutputType( address=txout['address'], amount=txout['satoshi'], script_type=proto.OutputScriptType.PAYTOADDRESS) out.confidential = proto.TxConfidentialAsset( asset=h2b(txout['asset_id'])[::-1], amount_blind=out_vbfs[i], asset_blind=out_abfs[i], nonce_privkey=h2b(txout['eph_keypair_sec'])) outs.append(out) signatures, serialized_tx = btc.sign_tx( self.client, 'Elements', ins, outs, prev_txes=None, details=proto.SignTx(version=2, lock_time=required_data['transaction'] ['transaction_locktime'])) # with open('tx.txt', 'w') as f: # f.write(serialized_tx.hex()) asset_commitments = [] value_commitments = [] tx = wally.tx_from_bytes( serialized_tx, wally.WALLY_TX_FLAG_USE_WITNESS | wally.WALLY_TX_FLAG_USE_ELEMENTS) for i in range(wally.tx_get_num_outputs(tx)): asset_commitments.append(wally.tx_get_output_asset(tx, i)) value_commitments.append(wally.tx_get_output_value(tx, i)) out_abfs.append(b'\x00' * 32) # FIXME: GDK enforcing blinding factors for fee out_vbfs.append(b'\x00' * 32) return json.dumps({ 'signatures': [sig.hex() + '01' for sig in signatures], 'vbfs': [vbf.hex() for vbf in out_vbfs], 'abfs': [abf.hex() for abf in out_abfs], 'asset_commitments': [commitment.hex() for commitment in asset_commitments], 'value_commitments': [commitment.hex() for commitment in value_commitments] })