public function handle_order_tx_submission($order, $incoming_tx, $user_bip32_key) { $this->CI->load->model('transaction_cache_model'); $currently_unsigned = strlen($order['partially_signed_transaction']) == 0; if ($currently_unsigned) { $start_tx = trim($order['unsigned_transaction']); } else { $start_tx = trim($order['partially_signed_transaction']); } $json = str_replace("'", '', $order['json_inputs']); $decode_current_tx = \BitWasp\BitcoinLib\RawTransaction::decode($start_tx); $decode_incoming_tx = \BitWasp\BitcoinLib\RawTransaction::decode($incoming_tx); // Does incoming tx match expected spend? $check = $this->CI->transaction_cache_model->check_if_expected_spend($decode_incoming_tx['vout'], $order['id']); if ($check !== $order['address']) { return 'Invalid transaction.'; } // General check that signatures match tx $validate = \BitWasp\BitcoinLib\RawTransaction::validate_signed_transaction($incoming_tx, $json); if ($validate == FALSE) { return 'Invalid signature.'; } $decode_redeem_script = \BitWasp\BitcoinLib\RawTransaction::decode_redeem_script($order['redeemScript']); if (!$currently_unsigned and $user_bip32_key['provider'] == 'JS') { // Need to build the sig from this tx into the last. $copy = $decode_incoming_tx; foreach ($copy['vin'] as $i => &$input) { $script = explode(" ", $input['scriptSig']['asm']); $sig1 = \BitWasp\BitcoinLib\RawTransaction::_encode_vint(strlen($script[1]) / 2) . $script[1]; $old_script = explode(" ", $decode_current_tx['vin'][$i]['scriptSig']['asm']); $sig2 = \BitWasp\BitcoinLib\RawTransaction::_encode_vint(strlen($old_script[1]) / 2) . $old_script[1]; $redeem_script = '4c' . \BitWasp\BitcoinLib\RawTransaction::_encode_vint(strlen($order['redeemScript']) / 2) . $order['redeemScript']; $input['scriptSig']['hex'] = '00' . $sig1 . $sig2 . $redeem_script; } $incoming_tx = \BitWasp\BitcoinLib\RawTransaction::encode($copy); // Now need to reorder sigs! $assoc = $this->associate_sigs_with_keys($incoming_tx, $json, $this->CI->bw_config->currencies[0]['crypto_magic_byte']); foreach ($copy['vin'] as $i => &$input) { $input['scriptSig']['hex'] = \BitWasp\BitcoinLib\RawTransaction::_apply_sig_scripthash_multisig($assoc[$i], array('public_keys' => $decode_redeem_script['keys'], 'script' => $order['redeemScript'])); } $incoming_tx = \BitWasp\BitcoinLib\RawTransaction::encode($copy); $decode_incoming_tx = \BitWasp\BitcoinLib\RawTransaction::decode($incoming_tx); } // Compare signatures! $old_sig_map = $this->associate_sigs_with_keys($start_tx, $json, $this->CI->bw_config->currencies[0]['crypto_magic_byte']); // Now check current signatures against users key. submittee must have signed. $key_sig_map = $this->associate_sigs_with_keys($incoming_tx, $json, $this->CI->bw_config->currencies[0]['crypto_magic_byte']); foreach ($key_sig_map as $i => $input_sig_map) { // If the number of sigs hasn't increased, or no sig from the current user exists.. if (count($old_sig_map) > 0 && count($input_sig_map) <= count($old_sig_map[$i]) or !isset($input_sig_map[$user_bip32_key['public_key']])) { return 'Incorrect signature!'; } } // Broadcast tx if fully signed! if (!$currently_unsigned) { $this->CI->transaction_cache_model->to_broadcast($incoming_tx); $this->sendrawtransaction($incoming_tx); } return TRUE; }
/** * verify a signed message * * @param $address * @param $signature * @param $message * @return bool * @throws \Exception */ public static function verifyMessage($address, $signature, $message) { $math = EccFactory::getAdapter(); $generator = EccFactory::getSecgCurves($math)->generator256k1(); // extract parameters $address = substr(hex2bin(self::base58_decode($address)), 0, -4); if (strlen($address) != 21 || $address[0] != hex2bin(self::magicByte())) { throw new \InvalidArgumentException('invalid Bitcoin address'); } $signature = base64_decode($signature, true); if ($signature === false) { throw new \InvalidArgumentException('invalid base64 signature'); } if (strlen($signature) != 65) { throw new \InvalidArgumentException('invalid signature length'); } $recoveryFlags = ord($signature[0]) - 27; if ($recoveryFlags < 0 || $recoveryFlags > 7) { throw new \InvalidArgumentException('invalid signature type'); } $isCompressed = ($recoveryFlags & 4) != 0; // message is <varInt><prefix><varInt><message> $messageHash = "Bitcoin Signed Message:\n" . hex2bin(RawTransaction::_encode_vint(strlen($message))) . $message; $messageHash = hash('sha256', hash('sha256', $messageHash, true), true); try { $pubkey = self::recoverPubKey($math->hexDec(bin2hex(substr($signature, 1, 32))), $math->hexDec(bin2hex(substr($signature, 33, 32))), $math->hexDec(bin2hex($messageHash)), $recoveryFlags, $generator); } catch (\Exception $e) { throw new \Exception("unable to recover key", 0, $e); } if ($pubkey === false) { throw new \Exception('unable to recover key'); } $point = $pubkey->getPoint(); // see that the key we recovered is for the address given if (!$isCompressed) { $pubBinStr = "" . str_pad(hex2bin(self::padHex($math->decHex($point->getX()))), 32, "", STR_PAD_LEFT) . str_pad(hex2bin(self::padHex($math->decHex($point->getY()))), 32, "", STR_PAD_LEFT); } else { $pubBinStr = ($math->mod($point->getY(), 2) == 0 ? "" : "") . str_pad(hex2bin(self::padHex($math->decHex($point->getX()))), 32, "", STR_PAD_LEFT); } $derivedAddress = hex2bin(self::magicByte()) . hash('ripemd160', hash('sha256', $pubBinStr, true), true); return $address === $derivedAddress; }