Example #1
0
 public function signMessage($address)
 {
     $input = Input::all();
     $output = array();
     if (!isset($input['message']) or trim($input['message']) == '') {
         $output['error'] = 'Message required';
         $output['result'] = false;
         return new Response($output, 400);
     }
     $get = PaymentAddress::where('uuid', $address)->orWhere('address', $address)->first();
     $found = false;
     if (!$get) {
         $output['error'] = 'Bitcoin address does not belong to server';
         $output['result'] = false;
         return new Response($output, 400);
     }
     $address = $get->address;
     $address_generator = app('Tokenly\\BitcoinAddressLib\\BitcoinAddressGenerator');
     $lib = new BitcoinLib();
     $priv_key = $address_generator->WIFPrivateKey($get->private_key_token);
     $priv_key = BitcoinLib::WIF_to_private_key($priv_key);
     $sign = $priv_key;
     try {
         $sign = $lib->signMessage($input['message'], $priv_key);
     } catch (Exception $e) {
         $sign = false;
     }
     if (!$sign) {
         $output['error'] = 'Error signing message';
         $output['result'] = false;
         return new Response($output, 500);
     }
     $output['result'] = $sign;
     return new Response($output);
 }
 /**
  * @param string $address
  * @param int    $value
  * @return $this
  * @throws \Exception
  */
 public function addRecipient($address, $value)
 {
     if (!BitcoinLib::validate_address($address)) {
         throw new \Exception("Invalid address [{$address}]");
     }
     // using this 'dirty' way of checking for a float since there's no other reliable way in PHP
     if (!is_int($value)) {
         throw new \Exception("Values should be in Satoshis (int)");
     }
     if ($value <= Blocktrail::DUST) {
         throw new \Exception("Values should be more than dust (" . Blocktrail::DUST . ")");
     }
     $this->addOutput(['address' => $address, 'value' => $value]);
     return $this;
 }
 public function testP2SHMultisig()
 {
     $j = 0;
     for ($i = 0; $i < 5; $i++) {
         $n = rand(1, 20);
         $m = rand(1, $n);
         $k = [];
         $pk_list = [];
         for ($i = 0; $i < $n; $i++) {
             $k[$i] = BitcoinLib::get_new_key_set(null, (bool) ($j++ % 2));
             $pk_list[] = $k[$i]['pubKey'];
         }
         $multisig = RawTransaction::create_multisig($m, $pk_list);
         $real = $this->client->createmultisig($m, $pk_list);
         $this->assertEquals($real['address'], $multisig['address']);
         $this->assertEquals($real['redeemScript'], $multisig['redeemScript']);
     }
 }
 /**
  * @param                                $primaryMnemonic
  * @param                                $primaryPassphrase
  * @param                                $backupMnemonic
  * @param array                          $blocktrailPublicKeys
  * @param BlockchainDataServiceInterface $bitcoinClient
  * @param string                         $network
  * @param bool                           $testnet
  * @throws \Exception
  */
 public function __construct($primaryMnemonic, $primaryPassphrase, $backupMnemonic, array $blocktrailPublicKeys, BlockchainDataServiceInterface $bitcoinClient, $network = 'btc', $testnet = false)
 {
     // normalize network and set bitcoinlib to the right magic-bytes
     list($this->network, $this->testnet) = $this->normalizeNetwork($network, $testnet);
     BitcoinLib::setMagicByteDefaults($this->network . ($this->testnet ? '-testnet' : ''));
     //create BIP32 keys for the Blocktrail public keys
     foreach ($blocktrailPublicKeys as $blocktrailKey) {
         $this->blocktrailPublicKeys[$blocktrailKey['keyIndex']] = BIP32Key::create($blocktrailKey['pubkey'], $blocktrailKey['path']);
     }
     //set the unspent output finder, using the given bitcoin data service provider
     $this->bitcoinClient = $bitcoinClient;
     $this->utxoFinder = new UnspentOutputFinder($this->bitcoinClient);
     // cleanup copy paste errors from mnemonics
     $primaryMnemonic = str_replace("  ", " ", str_replace("\r\n", " ", str_replace("\n", " ", trim($primaryMnemonic))));
     $backupMnemonic = str_replace("  ", " ", str_replace("\r\n", " ", str_replace("\n", " ", trim($backupMnemonic))));
     // convert the primary and backup mnemonics to seeds (using BIP39), then create private keys (using BIP32)
     $primarySeed = BIP39::mnemonicToSeedHex($primaryMnemonic, $primaryPassphrase);
     $backupSeed = BIP39::mnemonicToSeedHex($backupMnemonic, "");
     $this->primaryPrivateKey = BIP32Key::create(BIP32::master_key($primarySeed, $this->network, $this->testnet));
     $this->backupPrivateKey = BIP32Key::create(BIP32::master_key($backupSeed, $this->network, $this->testnet));
 }
 public function testMnemonicDecode()
 {
     $mnemonic = trim('teach start paradise collect blade chill gay childhood creek picture creator branch');
     $known_seed = 'dcb85458ec2fcaaac54b71fba90bd4a5';
     $known_secexp = '74b1f6c0caae485b4aeb2f26bab3cabdec4f0b432751bd454fe11b2d2907cbda';
     $known_mpk = '819519e966729f31e1855eb75133d9e7f0c31abaadd8f184870d62771c62c2e759406ace1dee933095d15e4c719617e252f32dc0465393055f867aee9357cd52';
     $known_addresses = ["", "", "", "", ""];
     $seed = Electrum::decode_mnemonic($mnemonic);
     $this->assertEquals($seed, $known_seed);
     $mpk = Electrum::generate_mpk($seed);
     $this->assertEquals($mpk, $known_mpk);
     $secexp = Electrum::stretch_seed($seed);
     $secexp = $secexp['seed'];
     $this->assertEquals($secexp, $known_secexp);
     $count_known_addresses = count($known_addresses);
     for ($i = 0; $i < $count_known_addresses; $i++) {
         $privkey = Electrum::generate_private_key($secexp, $i, 0);
         $address_private_deriv = BitcoinLib::private_key_to_address($privkey, $this->magic_byte);
         $public_deriv = Electrum::public_key_from_mpk($mpk, $i);
         $address_private_deriv = BitcoinLib::public_key_to_address($public_deriv, $this->magic_byte);
     }
 }
Example #6
0
 /**
  * Details
  * Buyer URI: purchases/details/<order_id>
  * Vendor URI: orders/details/<order_id>
  * Admin URI: admin/orders/<order_id>
  *
  * Order details page. Shows buyer/vendor/admin the details of the
  * the order.
  *
  * @param    int $order_id
  */
 public function details($order_id)
 {
     if (!(is_numeric($order_id) && $order_id >= 0)) {
         redirect('');
     }
     $data['order'] = $this->order_model->get($order_id);
     // no restriction on buyer/vendor
     if ($data['order'] == FALSE) {
         redirect('');
     }
     // Work out if the user is allowed to view this order.
     if (!$this->current_user->user_role == 'Admin' && !($this->current_user->user_id == $data['order']['buyer']['id']) && !($this->current_user->user_id == $data['order']['vendor']['id'])) {
         redirect('');
     }
     // Only allow access when the order is confirmed by the buyer.
     if ($data['order']['progress'] == '0') {
         redirect('');
     }
     if ($this->current_user->user_role == 'Buyer') {
         $data['action_page'] = 'purchases/details/' . $order_id;
         $data['cancel_page'] = 'purchases';
     } else {
         if ($this->current_user->user_role == 'Vendor') {
             $data['action_page'] = 'orders/details/' . $order_id;
             $data['cancel_page'] = 'orders';
         } else {
             if ($this->current_user->user_role == 'Admin') {
                 $data['action_page'] = 'admin/order/' . $order_id;
                 $data['cancel_page'] = 'admin/orders';
             }
         }
     }
     $this->load->model('transaction_cache_model');
     $this->load->model('bip32_model');
     $this->load->model('review_model');
     $child_key_id = $data['order'][strtolower($this->current_user->user_role) . '_public_key'];
     $data['my_multisig_key'] = $this->bip32_model->get_child_key($child_key_id);
     $data['wallet_salt'] = $this->users_model->wallet_salt($this->current_user->user_id);
     $this->load->library('bw_bitcoin');
     $html = $this->bw_bitcoin->js_html($data['order']['redeemScript'], $data['order']['unsigned_transaction']);
     if (is_array($data['my_multisig_key'])) {
         $data['signing_info'] = array('parent_extended_public_key' => $data['my_multisig_key']['parent_extended_public_key'], 'extended_public_key' => $data['my_multisig_key']['extended_public_key'], 'bip32_provider' => $data['my_multisig_key']['provider'], 'full_key_index' => "m/0'/0/" . $data['my_multisig_key']['key_index'], 'key_index' => "m/0'/0/" . $data['my_multisig_key']['key_index'], 'public_key' => $data['my_multisig_key']['public_key'], 'partially_signed_transaction' => $data['order']['partially_signed_transaction'], 'unsigned_transaction' => $data['order']['unsigned_transaction'], 'wallet_salt' => $data['wallet_salt'], 'crafted_html' => $html);
         $ident = strtolower($data['my_multisig_key']['provider']);
         $this->_partial("sign_form_output", "orders/details_form_" . $ident);
         if ($ident == 'js') {
             $data['header_meta'] = $this->load->view('orders/add_signature_js', $data['signing_info'], TRUE);
             $this->load->model('users_model');
         } elseif ($ident == 'onchain') {
             $this->load->library('onchainlib');
             $tx_crc = substr(hash('sha256', $data['order']['partially_signed_transaction'] == '' ? $data['order']['unsigned_transaction'] : $data['order']['partially_signed_transaction']), 0, 8);
             $data['onchain_sign'] = $this->onchainlib->sign_request($data['order']['id'], $tx_crc);
         }
     }
     // Only allow a vendor to refund, if the progress is either 3 or 4 (not yet dispatched)
     $data['can_refund'] = ($this->current_user->user_role == 'Vendor' and in_array($data['order']['progress'], array('3', '4')));
     $data['display_sign_form'] = (bool) (($data['order']['vendor_selected_upfront'] == '1' or $data['order']['vendor_selected_escrow'] == '0') and $data['order']['progress'] == '3' && $this->current_user->user_role == 'Buyer' or $data['order']['progress'] == '4' && $this->current_user->user_role == 'Vendor' or ($data['order']['vendor_selected_escrow'] == '1' and $data['order']['vendor_selected_upfront'] == '0' and $data['order']['progress'] == '4' && $this->current_user->user_role == 'Vendor' or $data['order']['progress'] == '5' && $this->current_user->user_role == 'Buyer') or ($data['order']['progress'] == '6' and $data['order']['partially_signed_transaction'] == '' or $data['order']['partially_signed_transaction'] !== '' and $data['order']['partially_signing_user_id'] !== $this->current_user->user_id) or ($data['order']['progress'] == '8' and $data['order']['partially_signed_transaction'] == '' or $data['order']['partially_signed_transaction'] !== '' and $data['order']['partially_signing_user_id'] !== $this->current_user->user_id));
     $data['display_sign_msg'] = $data['order']['partially_signed_transaction'] == '' ? 'Please add first signature.' : 'Please sign to complete transaction.';
     // Only allow access to the form handling script if the form is allowed to be displayed.
     if ($data['display_sign_form'] == TRUE) {
         // JS
         if ($this->input->post('submit_js_signed_transaction') == 'Submit Transaction') {
             if ($this->form_validation->run('submit_js_signed_transaction') == TRUE) {
                 $check = $this->bw_bitcoin->handle_order_tx_submission($data['order'], $this->input->post('js_transaction'), $data['my_multisig_key']);
                 if (is_string($check)) {
                     $data['invalid_transaction_error'] = $check;
                 } else {
                     if ($check == TRUE) {
                         if (strlen($data['order']['partially_signed_transaction']) > 0) {
                             $this->current_user->set_return_message('Transaction has been submitted, and will be processed shortly.', 'success');
                             redirect($data['action_page']);
                         } else {
                             if ($data['order']['progress'] == '3') {
                                 // Buyer must sign early before vendor dispatches.
                                 $update = array('partially_signed_transaction' => $this->input->post('js_transaction'), 'partially_signing_user_id' => $this->current_user->user_id, 'partially_signed_time' => time());
                                 $this->order_model->progress_order($order_id, '3', '4', $update);
                             } else {
                                 if ($data['order']['progress'] == '4') {
                                     // Vendor indicates they have dispatched.
                                     $update = array('partially_signed_transaction' => $this->input->post('js_transaction'), 'partially_signing_user_id' => $this->current_user->user_id, 'partially_signed_time' => time(), 'dispatched_time' => time(), 'dispatched' => '1');
                                     $this->order_model->progress_order($order_id, '4', '5', $update);
                                 } else {
                                     if ($data['order']['progress'] == '6') {
                                         $update = array('partially_signed_transaction' => $this->input->post('js_transaction'), 'partially_signing_user_id' => $this->current_user->user_id, 'partially_signed_time' => time());
                                         $this->order_model->update_order($order_id, $update);
                                     } else {
                                         if ($data['order']['progress'] == '8') {
                                             $update = array('partially_signed_transaction' => $this->input->post('js_transaction'), 'partially_signing_user_id' => $this->current_user->user_id, 'partially_signed_time' => time());
                                             $this->order_model->update_order($order_id, $update);
                                         }
                                     }
                                 }
                             }
                             $this->current_user->set_return_message('Your partially signed transaction has been saved!', 'success');
                             redirect($data['action_page']);
                         }
                     }
                 }
             }
         }
         // Manual
         if ($this->input->post('submit_signed_transaction') == 'Submit Transaction') {
             if ($this->form_validation->run('input_transaction') == TRUE) {
                 $validate = $this->bw_bitcoin->handle_order_tx_submission($data['order'], $this->input->post('partially_signed_transaction'), $data['my_multisig_key']);
                 if (is_string($validate)) {
                     $data['invalid_transaction_error'] = $validate;
                 } else {
                     if ($validate == TRUE) {
                         if (strlen($data['order']['partially_signed_transaction']) > 0) {
                             $this->current_user->set_return_message('Transaction has been submitted, and will be processed shortly.', 'success');
                             redirect($data['action_page']);
                         } else {
                             if ($data['order']['progress'] == '3') {
                                 // Buyer must sign early before vendor dispatches.
                                 $update = array('partially_signed_transaction' => $this->input->post('partially_signed_transaction'), 'partially_signing_user_id' => $this->current_user->user_id, 'partially_signed_time' => time());
                                 $this->order_model->progress_order($order_id, '3', '4', $update);
                             } else {
                                 if ($data['order']['progress'] == '4') {
                                     // Vendor indicates they have dispatched.
                                     $update = array('partially_signed_transaction' => $this->input->post('partially_signed_transaction'), 'partially_signing_user_id' => $this->current_user->user_id, 'partially_signed_time' => time(), 'dispatched_time' => time(), 'dispatched' => '1');
                                     $this->order_model->progress_order($order_id, '4', '5', $update);
                                 } else {
                                     if ($data['order']['progress'] == '6') {
                                         $update = array('partially_signed_transaction' => $this->input->post('partially_signed_transaction'), 'partially_signing_user_id' => $this->current_user->user_id, 'partially_signed_time' => time());
                                         $this->order_model->update_order($order_id, $update);
                                     } else {
                                         if ($data['order']['progress'] == '8') {
                                             $update = array('partially_signed_transaction' => $this->input->post('partially_signed_transaction'), 'partially_signing_user_id' => $this->current_user->user_id, 'partially_signed_time' => time());
                                             $this->order_model->update_order($order_id, $update);
                                         }
                                     }
                                 }
                             }
                             $this->current_user->set_return_message('Your partially signed transaction has been saved!', 'success');
                             redirect($data['action_page']);
                         }
                     }
                 }
             }
         }
     }
     $data['redeem_script'] = RawTransaction::decode_redeem_script($data['order']['redeemScript']);
     $data['addrs'] = array($data['order']['buyer_payout'] => 'buyer', $data['order']['vendor_payout'] => 'vendor');
     if (isset($data['order']['public_keys']['admin'])) {
         $data['addrs'][BitcoinLib::public_key_to_address($data['order']['public_keys']['admin']['public_key'], $this->bw_config->currencies[0]['crypto_magic_byte'])] = 'admin';
     }
     if (strlen($data['order']['partially_signed_transaction']) > 0) {
         $data['raw_tx'] = RawTransaction::decode($data['order']['partially_signed_transaction']);
         $data['signer'] = $this->accounts_model->get(array('id' => $data['order']['partially_signing_user_id']));
     } else {
         if (strlen($data['order']['unsigned_transaction']) > 0) {
             $data['raw_tx'] = RawTransaction::decode($data['order']['unsigned_transaction']);
         }
     }
     $checkStrangeAddress = function () use($data) {
         $tx_addrs = array();
         foreach ($data['raw_tx']['vout'] as $vout) {
             $tx_addrs[] = $vout['scriptPubKey']['addresses'][0];
         }
         return count($tx_addrs) != count(array_intersect($tx_addrs, array_keys($data['addrs'])));
     };
     $data['strange_address'] = isset($data['raw_tx']) ? $checkStrangeAddress() : FALSE;
     $data['fees']['shipping_cost'] = $data['order']['shipping_costs'];
     $data['fees']['fee'] = $data['order']['fees'];
     $data['fees']['escrow_fees'] = $data['order']['extra_fees'];
     $data['fees']['total'] = $data['order']['shipping_costs'] + $data['order']['fees'];
     if ($this->current_user->user_role == 'Buyer' && $data['order']['paid_time'] == '') {
         $this->load->library('ciqrcode');
         $data['payment_url'] = "bitcoin:{$data['order']['address']}?amount={$data['order']['order_price']}&message=Order+{$data['order']['id']}&label=Order+{$data['order']['id']}";
         $data['qr'] = $this->ciqrcode->generate_base64(array('data' => $data['payment_url']));
     }
     $data['page'] = 'orders/details';
     $data['title'] = 'Order Details: #' . $data['order']['id'];
     $this->_render($data['page'], $data);
 }
Example #7
0
 public function get_user_key_usage($user_id)
 {
     $query = $this->db->get_where('bip32_user_keys', array('user_id' => $user_id))->result_array();
     if (count($query) > 0) {
         foreach ($query as &$row) {
             $row['address'] = \BitWasp\BitcoinLib\BitcoinLib::public_key_to_address($row['public_key'], $this->bw_config->currencies[0]['crypto_magic_byte']);
         }
     }
     return $query;
 }
Example #8
0
 /**
  * Key To Address
  *
  * This function accepts a bip32 extended key, and converts it to a
  * bitcoin address.
  *
  * @param string $extended_key
  * @return string
  */
 public static function key_to_address($extended_key)
 {
     $import = self::import($extended_key);
     if ($import['type'] == 'public') {
         $public = $import['key'];
     } else {
         $public = BitcoinLib::private_key_to_public_key($import['key'], true);
     }
     // Convert the public key to the address.
     return BitcoinLib::public_key_to_address($public, $import['version']);
 }
 public function testSatoshiConversion()
 {
     $toSatoshi = [["0.00000001", "1", 1], [1.0E-8, "1", 1], ["0.29560000", "29560000", 29560000], [0.2956, "29560000", 29560000], ["1.0000009", "100000090", 100000090], [1.0000009, "100000090", 100000090], ["1.00000009", "100000009", 100000009], [1.00000009, "100000009", 100000009], ["21000000.00000001", "2100000000000001", 2100000000000001], [21000000.00000001, "2100000000000001", 2100000000000001], ["21000000.0000009", "2100000000000090", 2100000000000090], [21000000.0000009, "2100000000000090", 2100000000000090], ["21000000.00000009", "2100000000000009", 2100000000000009], [21000000.00000009, "2100000000000009", 2100000000000009], ["210000000.00000009", "21000000000000009", 21000000000000009], [210000000.0000001, "21000000000000009", 21000000000000009]];
     $toBTC = [["1", "0.00000001"], [1, "0.00000001"], ["29560000", "0.29560000"], [29560000, "0.29560000"], ["100000090", "1.00000090"], [100000090, "1.00000090"], ["100000009", "1.00000009"], [100000009, "1.00000009"], ["2100000000000001", "21000000.00000001"], [2100000000000001, "21000000.00000001"], ["2100000000000090", "21000000.00000090"], [2100000000000090, "21000000.00000090"], ["2100000000000009", "21000000.00000009"], [2100000000000009, "21000000.00000009"], ["21000000000000009", "210000000.00000009"], [21000000000000009, "210000000.00000009"], ["210000000000000009", "2100000000.00000009"], [210000000000000009, "2100000000.00000009"], ["2100000000000000009", "21000000000.00000009"], [2100000000000000009, "21000000000.00000009"]];
     foreach ($toSatoshi as $i => $test) {
         $btc = $test[0];
         $satoshiString = $test[1];
         $satoshiInt = $test[2];
         $string = BitcoinLib::toSatoshiString($btc);
         $this->assertEquals($satoshiString, $string, "[{$i}] {$btc} => {$satoshiString} =? {$string}");
         $this->assertTrue($satoshiString === $string, "[{$i}] {$btc} => {$satoshiString} ==? {$string}");
         $int = BitcoinLib::toSatoshi($btc);
         $this->assertEquals($satoshiInt, $int, "[{$i}] {$btc} => {$satoshiInt} =? {$int}");
         $this->assertTrue($satoshiInt === $int, "[{$i}] {$btc} => {$satoshiInt} ==? {$int}");
     }
     foreach ($toBTC as $i => $test) {
         $satoshi = $test[0];
         $btc = $test[1];
         $this->assertEquals($btc, BitcoinLib::toBTC($satoshi), "[{$i}] {$satoshi} => {$btc}");
         $this->assertTrue($btc === BitcoinLib::toBTC($satoshi), "[{$i}] {$satoshi} => {$btc}");
     }
 }
Example #10
0
 /**
  * this test requires / asumes that the test wallet it uses contains a balance
  *
  * we keep the wallet topped off with some coins,
  * but if some funny guy ever empties it or if you use your own API key to run the test then it needs to be topped off again
  *
  * @throws \Exception
  */
 public function testWalletTransactionWithoutMnemonics()
 {
     $client = $this->setupBlocktrailSDK();
     $primaryPrivateKey = BIP32::master_key(BIP39::mnemonicToSeedHex("give pause forget seed dance crawl situate hole keen", "password"), 'bitcoin', true);
     $wallet = $client->initWallet(["identifier" => "unittest-transaction", "primary_private_key" => $primaryPrivateKey, "primary_mnemonic" => false]);
     $this->assertEquals("unittest-transaction", $wallet->getIdentifier());
     $this->assertEquals("M/9999'", $wallet->getBlocktrailPublicKeys()[9999][1]);
     $this->assertEquals("tpubD9q6vq9zdP3gbhpjs7n2TRvT7h4PeBhxg1Kv9jEc1XAss7429VenxvQTsJaZhzTk54gnsHRpgeeNMbm1QTag4Wf1QpQ3gy221GDuUCxgfeZ", $wallet->getBlocktrailPublicKeys()[9999][0]);
     $this->assertEquals("tpubD9q6vq9zdP3gbhpjs7n2TRvT7h4PeBhxg1Kv9jEc1XAss7429VenxvQTsJaZhzTk54gnsHRpgeeNMbm1QTag4Wf1QpQ3gy221GDuUCxgfeZ", $wallet->getBlocktrailPublicKey("m/9999'")->key());
     $this->assertEquals("tpubD9q6vq9zdP3gbhpjs7n2TRvT7h4PeBhxg1Kv9jEc1XAss7429VenxvQTsJaZhzTk54gnsHRpgeeNMbm1QTag4Wf1QpQ3gy221GDuUCxgfeZ", $wallet->getBlocktrailPublicKey("M/9999'")->key());
     list($confirmed, $unconfirmed) = $wallet->getBalance();
     $this->assertGreaterThan(0, $confirmed + $unconfirmed, "positive unconfirmed balance");
     $this->assertGreaterThan(0, $confirmed, "positive confirmed balance");
     list($path, $address) = $wallet->getNewAddressPair();
     $this->assertTrue(strpos($path, "M/9999'/0/") === 0);
     $this->assertTrue(BitcoinLib::validate_address($address, false, null));
     $value = BlocktrailSDK::toSatoshi(0.0002);
     $txHash = $wallet->pay([$address => $value]);
     $this->assertTrue(!!$txHash);
     sleep(1);
     // sleep to wait for the TX to be processed
     try {
         $tx = $client->transaction($txHash);
     } catch (ObjectNotFound $e) {
         $this->fail("404 for tx[{$txHash}] [" . gmdate('Y-m-d H:i:s') . "]");
     }
     $this->assertTrue(!!$tx, "check for tx[{$txHash}] [" . gmdate('Y-m-d H:i:s') . "]");
     $this->assertEquals($txHash, $tx['hash']);
     $this->assertTrue(count($tx['outputs']) <= 2);
     $this->assertTrue(in_array($value, array_column($tx['outputs'], 'value')));
 }
    $line = trim(fgets(STDIN));
    $decode_redeem_script = RawTransaction::decode_redeem_script($line);
    if ($decode_redeem_script == FALSE) {
        echo "[ERROR]- Not a valid script!\n";
        unset($decode_redeem_script);
    } else {
        $redeem_script = $line;
        echo "Learned about {$decode_redeem_script['m']} of {$decode_redeem_script['n']} address: " . BitcoinLib::public_key_to_address($redeem_script, '05') . "\n";
    }
}
echo "Enter WIF encoded private keys: \n (1): ";
$private_keys = array();
while ("\n" != ($line = fgets(STDIN))) {
    $line = trim($line);
    $t = BitcoinLib::validate_WIF($line, '80');
    var_dump($t);
    if (BitcoinLib::validate_WIF($line, '80') == TRUE) {
        $private_keys[] = $line;
    } else {
        echo "Not a valid private key.\n";
    }
    echo " (" . (count($private_keys) + 1) . "): ";
}
// Initialize wallet
$wallet = array();
RawTransaction::private_keys_to_wallet($wallet, $private_keys, '00');
RawTransaction::redeem_scripts_to_wallet($wallet, array($redeem_script), '05');
$raw_transaction = '01000000018240b84b90a3ae326e13219dc8a2781661fa28e23129b26fea848dd8e01a0c520000000000ffffffff02d8270000000000001976a914b7119dfb9b5c8aa7157fec48fbe640ea347dc92b88ac584d0000000000001976a914592fb6dc8cf6cd561ec86fd5fbc2a140e5ac7bc988ac00000000';
$json = '[{"txid":"520c1ae0d88d84ea6fb22931e228fa611678a2c89d21136e32aea3904bb84082","vout":0,"scriptPubKey":"a914fb56f0d4845487cc9da00c3e91e63503245f151787","redeemScript":"52210385fae44cb9f0cf858e0d404baecf78d026fae4cc9dd4343b8562059473c2af7b2102f34a1b64155db258d3a910625bd80fae6adf67d7e5b5f3de03265a4208b552d841040fa9a86f3237237423dd8331dc481c0a949fe11594c5dfa0b54bdc105daa319f9de6547d97c22296d4211073e7cffa71c8d6cd4da639607ca64fca2705e562a353ae"}]';
$sign = RawTransaction::sign($wallet, $raw_transaction, $json);
print_r($sign);
<?php

use BitWasp\BitcoinLib\BitcoinLib;
require_once __DIR__ . '/../vendor/autoload.php';
$usage = "Usage: php {$argv[0]} <magic byte>\n\n";
$usage .= "Some sample bytes are on this list, but you can chose any 2 character byte.\n";
$usage .= "Bitcoin: 00\t\tTestnet: 6f \n";
$usage .= "Litecoin: 48 \n";
$usage .= "Namecoin: 52 \n";
$usage .= "Auroracoin: 17 \n";
if (count($argv) !== 2) {
    die($usage);
}
$magic_byte = $argv[1];
echo "Generated keypair: (this will not be saved, do not lose this data!)\n";
$keypair = BitcoinLib::get_new_key_set($magic_byte);
echo "Key pair: \n";
print_r($keypair);
echo "\n";
echo "\n";
<?php

use BitWasp\BitcoinLib\BitcoinLib;
require_once __DIR__ . '/../vendor/autoload.php';
$magic_byte = '00';
$keypair = BitcoinLib::get_new_key_set($magic_byte);
echo "Key pair: \n";
print_r($keypair);
echo "\n";
$compress = BitcoinLib::compress_public_key($keypair['pubKey']);
echo "Compressed public key: {$compress} \n";
$decompress = BitcoinLib::decompress_public_key($compress);
echo "Decompressed key info: \n";
print_r($decompress);
echo "\n";
$address = BitcoinLib::public_key_to_address($compress, $magic_byte);
echo "decoding {$address}\n";
echo BitcoinLib::base58_decode($address);
echo "\n\n";
$sc = '5357';
$ad = BitcoinLib::public_key_to_address($sc, '05');
echo $ad . "\n";
 /**
  * Log Key Usage
  *
  * @param    string $usage
  * @param    string $mpk
  * @param    int $iteration
  * @param    string $public_key
  * @param    int $order_id
  * @param    string $user_hash
  * @return    boolean
  */
 public function log_key_usage($usage, $mpk, $iteration, $public_key, $order_id = FALSE, $user_hash = FALSE)
 {
     if (!in_array($usage, array('fees', 'order'))) {
         return FALSE;
     }
     if ($usage == 'order' && $order_id == FALSE) {
         return FALSE;
     }
     if ($usage == 'fees' && $user_hash == FALSE) {
         return FALSE;
     }
     $coin = $this->bw_config->currencies[0];
     $address = BitcoinLib::public_key_to_address($public_key, $coin['crypto_magic_byte']);
     $order_id = $usage == 'order' ? $order_id : '';
     $user_hash = $usage == 'fees' ? $user_hash : '';
     $log = array('usage' => $usage, 'mpk' => $mpk, 'iteration' => $iteration, 'public_key' => $public_key, 'address' => $address, 'order_id' => $order_id, 'fees_user_hash' => $user_hash);
     return $this->db->insert('key_usage', $log) == TRUE ? TRUE : FALSE;
 }
 public function setup()
 {
     parent::setup();
     // ensure we're set to bitcoin-testnet and not bitcoin
     BitcoinLib::setMagicByteDefaults('bitcoin-testnet');
 }
Example #16
0
 /**
  * create checksum to verify ownership of the master primary key
  *
  * @return string[]     [address, signature]
  */
 protected function createChecksumVerificationSignature()
 {
     $import = BIP32::import($this->primaryPrivateKey->key());
     $public = $this->primaryPrivateKey->publicKey();
     $address = BitcoinLib::public_key_to_address($public, $import['version']);
     return [$address, BitcoinLib::signMessage($address, $import)];
 }
 public function a()
 {
     print_r(BitcoinLib::get_new_key_set('00'));
 }
Example #18
0
 /**
  * Redeem Scripts To Wallet
  *
  * This function extends on whatever data is in the $wallet array, by
  * adding script hash addresses to the wallet, and linking keys in the
  * multisignature address with keys in the wallet.
  * Adds each redeemScript to the referenced $wallet.
  *
  * @param array $wallet
  * @param array $redeem_scripts
  * @param null  $magic_p2sh_byte
  */
 public static function redeem_scripts_to_wallet(&$wallet, array $redeem_scripts = array(), $magic_p2sh_byte = null)
 {
     $magic_p2sh_byte = BitcoinLib::magicP2SHByte($magic_p2sh_byte);
     if (count($redeem_scripts) > 0) {
         foreach ($redeem_scripts as $script) {
             $decode = self::decode_redeem_script($script);
             if ($decode == false) {
                 continue;
             }
             $scripthash = BitcoinLib::hash160($script);
             $keys = array();
             foreach ($decode['keys'] as $key) {
                 $keyhash = BitcoinLib::hash160($key);
                 if (isset($wallet[$keyhash])) {
                     $keys[] = $wallet[$keyhash];
                 }
             }
             $wallet[$scripthash] = array('type' => 'scripthash', 'script' => $script, 'required_signature_count' => $decode['m'], 'address' => BitcoinLib::hash160_to_address($scripthash, $magic_p2sh_byte), 'public_keys' => $decode['keys'], 'keys' => $keys);
         }
     }
 }
 public function check_bitcoin_address($str)
 {
     return \BitWasp\BitcoinLib\BitcoinLib::validate_address($str, '05');
 }
Example #20
0
 /**
  * Dispute
  *
  * This controller shows either the disputes list (if $order_id is unset)
  * or a specified disputed order (set by $order_id).
  *
  * @param        int $order_id
  */
 public function dispute($order_id = NULL)
 {
     $this->load->library('form_validation');
     $this->load->model('order_model');
     $this->load->model('disputes_model');
     // If no order is specified, load the list of disputes.
     if ($order_id == NULL) {
         $data['page'] = 'admin/disputes_list';
         $data['title'] = 'Active Disputes';
         $data['disputes'] = $this->disputes_model->disputes_list();
     } else {
         $data['dispute'] = $this->disputes_model->get_by_order_id($order_id);
         // If the dispute cannot be found, redirect to the dispute list.
         if ($data['dispute'] == FALSE) {
             redirect('admin/disputes');
         }
         $data['page'] = 'admin/dispute';
         $data['title'] = "Disputed Order #{$order_id}";
         // Load the order information.
         $data['current_order'] = $this->order_model->get($order_id);
         // Work out whether the vendor or buyer is disputing.
         $data['disputing_user'] = $data['dispute']['disputing_user_id'] == $data['current_order']['buyer']['id'] ? $data['current_order']['buyer'] : $data['current_order']['vendor'];
         $data['other_user'] = $data['dispute']['other_user_id'] == $data['current_order']['buyer']['id'] ? $data['current_order']['buyer'] : $data['current_order']['vendor'];
         // If the message is updated:
         if ($this->input->post('post_dispute_message') == 'Post Message') {
             if ($this->form_validation->run('add_dispute_update') == TRUE) {
                 // Update the dispute record.
                 $update = array('posting_user_id' => $this->current_user->user_id, 'order_id' => $order_id, 'dispute_id' => $data['dispute']['id'], 'message' => $this->input->post('update_message'));
                 if ($this->disputes_model->post_dispute_update($update) == TRUE) {
                     redirect('admin/dispute/' . $order_id);
                 }
             }
         }
         // Resolution:
         $data['transaction_fee'] = 0.0001;
         $data['admin_fee'] = $data['current_order']['fees'] + $data['current_order']['extra_fees'] - $data['transaction_fee'];
         $data['user_funds'] = (double) ($data['current_order']['order_price'] - $data['admin_fee'] - $data['transaction_fee']);
         if ($this->input->post('resolve_dispute') !== null) {
             if ($this->form_validation->run('admin_resolve_dispute') == TRUE) {
                 if ($this->input->post('resolve_dispute_id') == $data['current_order']['id']) {
                     if ($this->input->post('relinquish_fee') == '1') {
                         $data['admin_fee'] = 0;
                         $data['user_funds'] = (double) ($data['current_order']['order_price'] - $data['admin_fee'] - $data['transaction_fee']);
                     }
                     if ($data['current_order']['vendor_selected_escrow'] == '1') {
                         $pay_buyer_amount = $this->input->post('pay_buyer');
                         $pay_vendor_amount = $this->input->post('pay_vendor');
                         $sum = $pay_buyer_amount + $pay_vendor_amount;
                         $epsilon = 1.0E-8;
                         // Must total user funds available
                         if (abs($sum - $data['user_funds']) < $epsilon) {
                             $tx_outs = array();
                             // Add outputs for the sites fee, buyer, and vendor.
                             if ($data['admin_fee'] > 0) {
                                 $admin_address = BitcoinLib::public_key_to_address($data['current_order']['public_keys']['admin']['public_key'], $this->bw_config->currencies[0]['crypto_magic_byte']);
                                 $tx_outs[$admin_address] = (double) $data['admin_fee'];
                             }
                             if ($pay_buyer_amount > 0) {
                                 $tx_outs[$data['current_order']['buyer_payout']] = (double) $pay_buyer_amount;
                             }
                             if ($pay_vendor_amount > 0) {
                                 $tx_outs[$data['current_order']['vendor_payout']] = (double) $pay_vendor_amount;
                             }
                             // Create spend transaction and redirect, otherwise display an error
                             $create_spend_transaction = $this->order_model->create_spend_transaction($data['current_order']['address'], $tx_outs, $data['current_order']['redeemScript']);
                             if ($create_spend_transaction == TRUE) {
                                 // Notify users by way of a dispute update
                                 $this->disputes_model->post_dispute_update(array('posting_user_id' => '', 'order_id' => $order_id, 'dispute_id' => $data['dispute']['id'], 'message' => 'New transaction on order page.'));
                                 redirect('admin/dispute/' . $order_id);
                             } else {
                                 $data['returnMessage'] = $create_spend_transaction;
                             }
                         } else {
                             $data['amount_error'] = 'The User Funds amount must be completely spread between both users.';
                         }
                     } else {
                         if ($this->order_model->progress_order($data['current_order']['id'], '6') == TRUE) {
                             $update = array('posting_user_id' => '', 'order_id' => $order_id, 'dispute_id' => $data['dispute']['id'], 'message' => 'Dispute closed by admin.');
                             $this->disputes_model->post_dispute_update($update);
                             $this->disputes_model->set_final_response($data['current_order']['id']);
                             redirect('admin/dispute/' . $order_id);
                         }
                     }
                 }
             }
         }
     }
     $this->_render($data['page'], $data);
 }
 /**
  * Decode Mnemonic
  *
  * This function decodes a string of 12 words to convert to the electrum
  * seed. This is an implementation of http://tools.ietf.org/html/rfc1751,
  * which is how electrum generates a 128-bit key from 12 words.
  *
  * @param    string $words
  * @return    string
  */
 public static function decode_mnemonic($words)
 {
     $math = EccFactory::getAdapter();
     $words = explode(" ", $words);
     $out = '';
     $n = 1626;
     for ($i = 0; $i < count($words) / 3; $i++) {
         $a = 3 * $i;
         list($word1, $word2, $word3) = array($words[$a], $words[$a + 1], $words[$a + 2]);
         $index_w1 = array_search($word1, self::$words);
         $index_w2 = array_search($word2, self::$words) % $n;
         $index_w3 = array_search($word3, self::$words) % $n;
         $x = $index_w1 + $n * $math->mod($index_w2 - $index_w1, $n) + $n * $n * $math->mod($index_w3 - $index_w2, $n);
         $out .= BitcoinLib::hex_encode($x);
     }
     return $out;
 }
 public function testSignMessageDataSetAgainstRPC()
 {
     if (!getenv('BITCOINLIB_TEST_AGAINST_RPC')) {
         return $this->markTestSkipped("Not testing against RPC");
     }
     // special case, when undefined we do 1, otherwise we do ENV * 5 (50 on travis)
     $cnt = getenv('BITCOINLIB_EXTENSIVE_TESTING') ? getenv('BITCOINLIB_EXTENSIVE_TESTING') * 5 : 1;
     $rpcHost = getenv('BITCOINLIB_RPC_HOST') ?: '127.0.0.1';
     $rpcUser = getenv('BITCOINLIB_RPC_USER') ?: 'bitcoinrpc';
     $rpcPassword = getenv('BITCOINLIB_RPC_PASSWORD') ?: '6Wk1SYL7JmPYoUeWjYRSdqij4xrM5rGBvC4kbJipLVJK';
     $rpc = new Jsonrpcclient(['url' => "http://{$rpcUser}:{$rpcPassword}@{$rpcHost}:8332"]);
     if ($rpc->getinfo() == null) {
         $this->fail("Can't connect to bitcoind");
     }
     $data = json_decode(file_get_contents(__DIR__ . "/data/signverify.json"), true);
     $data = array_map(function ($k) use($data) {
         return $data[$k];
     }, (array) array_rand($data, $cnt));
     foreach ($data as $row) {
         $privKey = BitcoinLib::WIF_to_private_key($row['wif']);
         $signature = BitcoinLib::signMessage($row['address'], $privKey);
         $this->assertTrue(!!$signature);
         $this->assertTrue(BitcoinLib::verifyMessage($row['address'], $signature, $row['address']));
         $this->assertTrue($rpc->verifymessage($row['address'], $signature, $row['address']));
     }
 }
// Create derived key from master key + derivation
$key = BIP32::build_key($master, $def);
// Display private extended key and the address that's derived from it.
echo "Generated key: note that all depth=1 keys are hardened. \n {$key[1]}        : {$key[0]}\n";
echo "             : " . BIP32::key_to_address($key[0]) . "\n";
// Convert the extended private key to the public key, and display the address that's derived from it.
$pub = BIP32::extended_private_to_public($key);
echo "Public key\n {$pub[1]}        : {$pub[0]}\n";
echo "             : " . BIP32::key_to_address($pub[0]) . "\n";
/////////////////////////////
// We're gonna spent the first txout from this tx:
//  https://www.blocktrail.com/BTC/tx/4a2231e13182cdb64fa2f9aae38fca46549891e9dc15e8aaf484d82fc6e0a1d8
// Set up inputs here
$inputs = array(array('txid' => '4a2231e13182cdb64fa2f9aae38fca46549891e9dc15e8aaf484d82fc6e0a1d8', 'vout' => 0));
// Set up outputs here
$outputs = array('1KuE17Fbcdsn3Ns5T9Wzi1epurRnKC9qVr' => BitcoinLib::toSatoshi(0.0004));
////////////////////////////
// Parameters for signing.
// Create JSON inputs parameter
// - These can come from bitcoind, or just knowledge of the txid/vout/scriptPubKey,
//   and redeemScript if needed.
$json_inputs = json_encode(array(array('txid' => '4a2231e13182cdb64fa2f9aae38fca46549891e9dc15e8aaf484d82fc6e0a1d8', 'vout' => 0, 'scriptPubKey' => '76a914' . 'bf012bde5bd12eb7f9a66de5697b241b65a9a3c9' . '88ac')));
// build wallet from private key(s)
$wallet = array();
BIP32::bip32_keys_to_wallet($wallet, array($key), '00');
// Create raw transaction
$raw_transaction = RawTransaction::create($inputs, $outputs);
// Sign the transaction
$signed = RawTransaction::sign($wallet, $raw_transaction, $json_inputs);
print_r($signed);
echo "\n";
Example #24
0
<?php

use BitWasp\BitcoinLib\BitcoinLib;
use BitWasp\BitcoinLib\RawTransaction;
require_once __DIR__ . '/../vendor/autoload.php';
$m = 2;
$public_keys = array('0379ddc228d8c44a85ae30c877a6b037ec3d627e0507f223a0412790a83a46cd5f', '024d1cf2ca917f4d679fc02df2a39c0a8110a1b6935b27ae6762a0ceeec7752801', '0258f70f6400aa6f60ff0d21c3aaf1ca236d177877d2b9ad9d2c55280e375ab2d2');
// Create redeem script
$redeem_script = RawTransaction::create_redeem_script($m, $public_keys);
// Obtain 20-byte hash of script
$hash160 = BitcoinLib::hash160($redeem_script);
// Convert to address with version 0x05.
$address = BitcoinLib::hash160_to_address($hash160, '05');
// Display data
$c = 0;
echo "Public Keys\n";
for ($i = 0; $i < count($public_keys); $i++) {
    echo "{$i} : " . $public_keys[$i] . "\n";
}
echo "\nRedeem Script\n";
echo "{$redeem_script}\n\n";
echo "Hash160\n";
echo "{$hash160}\n\n";
echo "Address\n";
echo "{$address}\n\n";
$inputs = array(array('txid' => '6737e1355be0566c583eecd48bf8a5e1fcdf2d9f51cc7be82d4393ac9555611c', 'vout' => 0, 'value' => 0.0002, 'scriptPubKey' => '76a9147e3f939e8ded8c0d93695310d6d481ae5da3961688ac'));
// sum up the total amount of coins we're spending
$inputsTotal = 0;
foreach ($inputs as $input) {
    $inputsTotal += $input['value'];
}
// fixed fee
$fee = 0.0001;
// information of who we're sending coins to and how much
$to = '1PGa6cMAzzrBpTtfvQTzX5PmUxsDiFzKyW';
$send = 5.0E-5;
// calculate change
$change = $inputsTotal - $send - $fee;
// this is our own address
$changeAddress = "1CWYJZ4vSoemSCrfBvXreqEtojEeCUeKw3";
// create ouputs, one to recipient and one to change
$outputs = array($to => BitcoinLib::toSatoshi($send), $changeAddress => BitcoinLib::toSatoshi($change));
// import private key
$wallet = array();
RawTransaction::private_keys_to_wallet($wallet, array('L2V4QgXVUyWVoMGejTj7PrRUUCEi9D9Y1AhUM8E6f5yJm7gemgN6'), '00');
// crate unsigned raw transaction
$raw_transaction = RawTransaction::create($inputs, $outputs);
// sign the transaction
// to broadcast transaction take this value and `bitcoin-cli sendrawtransaction <hex>`
$sign = RawTransaction::sign($wallet, $raw_transaction, json_encode($inputs));
print_r($sign);
echo "\n";
// set the transaction hash from the raw transaction
$txid = RawTransaction::txid_from_raw($sign['hex']);
print_r($txid);
echo "\n";
 public function testP2SHMultisig2()
 {
     $n = 3;
     $m = 2;
     $privKeys = ["a56a29f79648d95c5666989c9b2b8d40bfe29c4f65b6fbc3e28ed15f8bc46691", "df3fa8db488c6ab6eb31f6b8979dcffd9a7c334196db88b1705bf8bfada41bb2", "2c44e5a2b83abded4e02aae1c3c02a95bf68a4ca56b5473c7f55b8940a5dcfa6"];
     $pubKeys = array_map(function ($privKey) {
         return BitcoinLib::private_key_to_public_key($privKey, true);
     }, $privKeys);
     $pubKeys = RawTransaction::sort_multisig_keys($pubKeys);
     $multisig = RawTransaction::create_multisig($m, $pubKeys);
     $this->assertEquals("3BMH67dedFZTbbtMQ3e7nnKEzHfkwB6VpU", $multisig['address']);
 }
<?php

use BitWasp\BitcoinLib\BitcoinLib;
use BitWasp\BitcoinLib\Electrum;
require_once __DIR__ . '/../vendor/autoload.php';
$magic_byte = '00';
$string = trim('teach start paradise collect blade chill gay childhood creek picture creator branch');
$seed = Electrum::decode_mnemonic($string);
echo "Words: {$string}\n";
echo "Seed:  {$seed}\n";
$secexp = Electrum::stretch_seed($seed);
$secexp = $secexp['seed'];
echo "Secret Exponent: {$secexp}\n";
$mpk = Electrum::generate_mpk($seed);
echo "MPK: {$mpk}\n";
for ($i = 0; $i < 5; $i++) {
    $privkey = Electrum::generate_private_key($secexp, $i, 0);
    echo "Private key: {$privkey}\n";
    echo "Private WIF: " . BitcoinLib::private_key_to_WIF($privkey, FALSE, $magic_byte) . "\n";
    $public_key = Electrum::public_key_from_mpk($mpk, $i);
    echo "Public Key: {$public_key}\n";
    $address = BitcoinLib::public_key_to_address($public_key, $magic_byte);
    echo "Public derivation: {$address}.\n";
    $address = BitcoinLib::private_key_to_address($privkey, $magic_byte);
    echo "Private derivation: {$address}.\n";
    echo "-----------\n";
}
 public function setup()
 {
     // ensure we're set to bitcoin and not bitcoin-testnet
     BitcoinLib::setMagicByteDefaults('bitcoin');
 }
 /**
  * verify a message signed bitcoin-core style
  *
  * @param  string           $message
  * @param  string           $address
  * @param  string           $signature
  * @return boolean
  */
 public function verifyMessage($message, $address, $signature)
 {
     // we could also use the API instead of the using BitcoinLib to verify
     // $this->client->post("verify_message", null, ['message' => $message, 'address' => $address, 'signature' => $signature])['result'];
     try {
         return BitcoinLib::verifyMessage($address, $signature, $message);
     } catch (\Exception $e) {
         return false;
     }
 }
 * address: mhsywR248h21gCB8oSwse5tmFSPvo9d5ML
 * priv:    cMps8Dg4Z1ThcwvPiPpshR6cbosYoTrgUwgLcFasBSxsdLHwzoUK
 * pub:     02ab1fae8dacd465460ad8e0c08cb9c25871782aa539a58b65f9bf1264c355d098
 *
 * address: mh7gsCxi4pcuNyHU9aWD9pGogHNJJZcCta
 * priv:    cNn72iUvQhuzZCWg3TC31fvyNDYttL8emHgMcFJzhF4xnFo8LYCk
 * pub:     02dc43b58ee5313d1969b939718d2c8104a3365d45f12f91753bfc950d16d3e82e
 *
 * 2of3 address: 2N1zEScjXeBDX2Gy4c6ojLTfqjRjSvf7iEC
 * 2of3 redeem:  522103c0b1fd07752ebdd43c75c0a60d67958eeac8d4f5245884477eae094c4361418d2102ab1fae8dacd465460ad8e0c08cb9c25871782aa539a58b65f9bf1264c355d0982102dc43b58ee5313d1969b939718d2c8104a3365d45f12f91753bfc950d16d3e82e53ae
 *
 * funded in TX: 83c5c88e94d9c518f314e30ca0529ab3f8e5e4f14a8936db4a32070005e3b61f
 */
$redeem_script = "522103c0b1fd07752ebdd43c75c0a60d67958eeac8d4f5245884477eae094c4361418d2102ab1fae8dacd465460ad8e0c08cb9c25871782aa539a58b65f9bf1264c355d0982102dc43b58ee5313d1969b939718d2c8104a3365d45f12f91753bfc950d16d3e82e53ae";
$inputs = array(array("txid" => "83c5c88e94d9c518f314e30ca0529ab3f8e5e4f14a8936db4a32070005e3b61f", "vout" => 0, "scriptPubKey" => "a9145fe34588f475c5251ff994eafb691a5ce197d18b87", "redeemScript" => $redeem_script));
$outputs = array("n3P94USXs7LzfF4BKJVyGv2uCfBQRbvMZJ" => BitcoinLib::toSatoshi(0.0001));
$raw_transaction = RawTransaction::create($inputs, $outputs);
/*
 * sign with first key
 */
$wallet = array();
RawTransaction::private_keys_to_wallet($wallet, array("cV2BRcdtWoZMSovYCpoY9gyvjiVK5xufpAwdAFk1jdonhGZq1cCm"));
RawTransaction::redeem_scripts_to_wallet($wallet, array($redeem_script));
$sign = RawTransaction::sign($wallet, $raw_transaction, json_encode($inputs));
print_r($sign);
var_dump(2 == $sign['req_sigs'], 1 == $sign['sign_count'], 'false' === $sign['complete']);
/*
 * sign with second key
 */
$wallet = array();
RawTransaction::private_keys_to_wallet($wallet, array("cMps8Dg4Z1ThcwvPiPpshR6cbosYoTrgUwgLcFasBSxsdLHwzoUK"));