/** * create a new wallet * - will generate a new primary seed (with password) and backup seed (without password) * - send the primary seed (BIP39 'encrypted') and backup public key to the server * - receive the blocktrail co-signing public key from the server * * Either takes one argument: * @param array $options * * Or takes three arguments (old, deprecated syntax): * (@nonPHP-doc) @param $identifier * (@nonPHP-doc) @param $password * (@nonPHP-doc) @param int $keyIndex override for the blocktrail cosigning key to use * * @return array[WalletInterface, (string)primaryMnemonic, (string)backupMnemonic] * @throws \Exception */ public function createNewWallet($options) { if (!is_array($options)) { $args = func_get_args(); $options = ["identifier" => $args[0], "password" => $args[1], "key_index" => isset($args[2]) ? $args[2] : null]; } $identifier = $options['identifier']; $password = isset($options['passphrase']) ? $options['passphrase'] : (isset($options['password']) ? $options['password'] : null); $keyIndex = isset($options['key_index']) ? $options['key_index'] : 0; $walletPath = WalletPath::create($keyIndex); $storePrimaryMnemonic = isset($options['store_primary_mnemonic']) ? $options['store_primary_mnemonic'] : null; if (isset($options['primary_mnemonic']) && $options['primary_private_key']) { throw new \InvalidArgumentException("Can't specify Primary Mnemonic and Primary PrivateKey"); } $primaryMnemonic = null; $primaryPrivateKey = null; if (!isset($options['primary_mnemonic']) && !isset($options['primary_private_key'])) { if (!$password) { throw new \InvalidArgumentException("Can't generate Primary Mnemonic without a passphrase"); } else { // create new primary seed list($primaryMnemonic, $primarySeed, $primaryPrivateKey) = $this->newPrimarySeed($password); if ($storePrimaryMnemonic !== false) { $storePrimaryMnemonic = true; } } } else { if (isset($options['primary_mnemonic'])) { $primaryMnemonic = $options['primary_mnemonic']; } else { if (isset($options['primary_private_key'])) { $primaryPrivateKey = $options['primary_private_key']; } } } if ($storePrimaryMnemonic && $primaryMnemonic && !$password) { throw new \InvalidArgumentException("Can't store Primary Mnemonic on server without a passphrase"); } if ($primaryPrivateKey) { if (is_string($primaryPrivateKey)) { $primaryPrivateKey = [$primaryPrivateKey, "m"]; } } else { $primaryPrivateKey = BIP32::master_key(BIP39::mnemonicToSeedHex($primaryMnemonic, $password), 'bitcoin', $this->testnet); } if (!$storePrimaryMnemonic) { $primaryMnemonic = false; } // create primary public key from the created private key $primaryPublicKey = BIP32::build_key($primaryPrivateKey, (string) $walletPath->keyIndexPath()->publicPath()); if (isset($options['backup_mnemonic']) && $options['backup_public_key']) { throw new \InvalidArgumentException("Can't specify Backup Mnemonic and Backup PublicKey"); } $backupMnemonic = null; $backupPublicKey = null; if (!isset($options['backup_mnemonic']) && !isset($options['backup_public_key'])) { list($backupMnemonic, $backupSeed, $backupPrivateKey) = $this->newBackupSeed(); } else { if (isset($options['backup_mnemonic'])) { $backupMnemonic = $options['backup_mnemonic']; } else { if (isset($options['backup_public_key'])) { $backupPublicKey = $options['backup_public_key']; } } } if ($backupPublicKey) { if (is_string($backupPublicKey)) { $backupPublicKey = [$backupPublicKey, "m"]; } } else { $backupPublicKey = BIP32::extended_private_to_public(BIP32::master_key(BIP39::mnemonicToSeedHex($backupMnemonic, ""), 'bitcoin', $this->testnet)); } // create a checksum of our private key which we'll later use to verify we used the right password $checksum = BIP32::key_to_address($primaryPrivateKey[0]); // send the public keys to the server to store them // and the mnemonic, which is safe because it's useless without the password $data = $this->_createNewWallet($identifier, $primaryPublicKey, $backupPublicKey, $primaryMnemonic, $checksum, $keyIndex); // received the blocktrail public keys $blocktrailPublicKeys = $data['blocktrail_public_keys']; $wallet = new Wallet($this, $identifier, $primaryMnemonic, [$keyIndex => $primaryPublicKey], $backupPublicKey, $blocktrailPublicKeys, $keyIndex, $this->network, $this->testnet, $checksum); $wallet->unlock($options); // return wallet and backup mnemonic return [$wallet, $primaryMnemonic, $backupMnemonic, $blocktrailPublicKeys]; }
require_once __DIR__ . '/../vendor/autoload.php'; // Fixed seed and derivation to test with $seed = '41414141414141414141414141414141414141'; $def = "0'/0"; // Create master key from seed $master = BIP32::master_key($seed); echo "\nMaster key\n m : {$master[0]} \n"; // 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();
protected function _createTestWallet(BlocktrailSDKInterface $client, $identifier, $passphrase, $primaryMnemonic, $backupMnemonic, $readOnly = false) { $walletPath = WalletPath::create(9999); $seed = BIP39::mnemonicToSeedHex($primaryMnemonic, $passphrase); $primaryPrivateKey = BIP32::master_key($seed, 'bitcoin', true); $primaryPublicKey = BIP32::build_key($primaryPrivateKey, (string) $walletPath->keyIndexPath()->publicPath()); $seed = BIP39::mnemonicToSeedHex($backupMnemonic, ""); $backupPrivateKey = BIP32::master_key($seed, 'bitcoin', true); $backupPublicKey = BIP32::build_key($backupPrivateKey, (string) "M"); $testnet = true; $checksum = BIP32::key_to_address($primaryPrivateKey[0]); $result = $client->_createNewWallet($identifier, $primaryPublicKey, $backupPublicKey, $primaryMnemonic, $checksum, 9999); $blocktrailPublicKeys = $result['blocktrail_public_keys']; $keyIndex = $result['key_index']; $wallet = new Wallet($client, $identifier, $primaryMnemonic, [$keyIndex => $primaryPublicKey], $backupPublicKey, $blocktrailPublicKeys, $keyIndex, 'bitcoin', $testnet, $checksum); if (!$readOnly) { $wallet->unlock(['password' => $passphrase]); } return $wallet; }
/** * unlock wallet so it can be used for payments * * @param $options ['primary_private_key' => key] OR ['passphrase' => pass] * @param callable $fn * @return bool * @throws \Exception */ public function unlock($options, callable $fn = null) { // explode the wallet data $password = isset($options['passphrase']) ? $options['passphrase'] : (isset($options['password']) ? $options['password'] : null); $primaryMnemonic = $this->primaryMnemonic; $primaryPrivateKey = isset($options['primary_private_key']) ? $options['primary_private_key'] : null; if ($primaryMnemonic && $primaryPrivateKey) { throw new \InvalidArgumentException("Can't specify Primary Mnemonic and Primary PrivateKey"); } if (!$primaryMnemonic && !$primaryPrivateKey) { throw new \InvalidArgumentException("Can't init wallet with Primary Mnemonic or Primary PrivateKey"); } if ($primaryMnemonic && !$password) { throw new \InvalidArgumentException("Can't init wallet with Primary Mnemonic without a passphrase"); } if ($primaryPrivateKey) { if (is_string($primaryPrivateKey)) { $primaryPrivateKey = [$primaryPrivateKey, "m"]; } } else { // convert the mnemonic to a seed using BIP39 standard $primarySeed = BIP39::mnemonicToSeedHex($primaryMnemonic, $password); // create BIP32 private key from the seed $primaryPrivateKey = BIP32::master_key($primarySeed, $this->network, $this->testnet); } $this->primaryPrivateKey = BIP32Key::create($primaryPrivateKey); // create checksum (address) of the primary privatekey to compare to the stored checksum $checksum = BIP32::key_to_address($primaryPrivateKey[0]); if ($checksum != $this->checksum) { throw new \Exception("Checksum [{$checksum}] does not match [{$this->checksum}], most likely due to incorrect password"); } $this->locked = false; // if the response suggests we should upgrade to a different blocktrail cosigning key then we should if (isset($data['upgrade_key_index'])) { $this->upgradeKeyIndex($data['upgrade_key_index']); } if ($fn) { $fn($this); $this->lock(); } }