public function testWalletPath() { $this->assertEquals(new WalletPath(), WalletPath::create()); $w = WalletPath::create(); $this->assertTrue($w->path() instanceof BIP32Path); $this->assertTrue($w->keyIndexPath() instanceof BIP32Path); $this->assertTrue($w->backupPath() instanceof BIP32Path); $this->assertTrue($w->keyIndexBackupPath() instanceof BIP32Path); $w = WalletPath::create(); $this->assertEquals("m/0'/0/0", (string) $w->path()); $this->assertEquals("m/0'", (string) $w->keyIndexPath()); $this->assertEquals("m/0/0/0", (string) $w->backupPath()); $this->assertEquals("m/0", (string) $w->keyIndexBackupPath()); $w = $w->address(1); $this->assertEquals("m/0'/0/1", (string) $w->path()); $this->assertEquals("m/0'", (string) $w->keyIndexPath()); $this->assertEquals("m/0/0/1", (string) $w->backupPath()); $this->assertEquals("m/0", (string) $w->keyIndexBackupPath()); $w = WalletPath::create(1, 1, 1); $this->assertEquals("m/1'/1/1", (string) $w->path()); $this->assertEquals("m/1'", (string) $w->keyIndexPath()); $this->assertEquals("m/1/1/1", (string) $w->backupPath()); $this->assertEquals("m/1", (string) $w->keyIndexBackupPath()); $w = $w->address(2); $this->assertEquals("m/1'/1/2", (string) $w->path()); $this->assertEquals("m/1'", (string) $w->keyIndexPath()); $this->assertEquals("m/1/1/2", (string) $w->backupPath()); $this->assertEquals("m/1", (string) $w->keyIndexBackupPath()); }
/** * 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]; }
/** * create a batch of multisig addresses * * @param $start * @param $count * @param $keyIndex * @return array */ protected function createBatchAddresses($start, $count, $keyIndex) { $addresses = array(); for ($i = 0; $i < $count; $i++) { //create a path subsequent address $path = (string) WalletPath::create($keyIndex, $_chain = 0, $start + $i)->path()->publicPath(); list($address, $redeem) = $this->createAddress($path); $addresses[$address] = array('redeem' => $redeem, 'path' => $path); if ($this->debug) { echo "."; } } return $addresses; }
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; }
/** * upgrade wallet to different blocktrail cosign key * * @param $keyIndex * @return bool * @throws \Exception */ public function upgradeKeyIndex($keyIndex) { if ($this->locked) { throw new \Exception("Wallet needs to be unlocked to upgrade key index"); } $walletPath = WalletPath::create($keyIndex); // do the upgrade to the new 'key_index' $primaryPublicKey = BIP32::extended_private_to_public(BIP32::build_key($this->primaryPrivateKey->tuple(), (string) $walletPath->keyIndexPath())); $result = $this->sdk->upgradeKeyIndex($this->identifier, $keyIndex, $primaryPublicKey); $this->primaryPublicKeys[$keyIndex] = BIP32Key::create($primaryPublicKey); $this->keyIndex = $keyIndex; $this->walletPath = $walletPath; // update the blocktrail public keys foreach ($result['blocktrail_public_keys'] as $keyIndex => $pubKey) { if (!isset($this->blocktrailPublicKeys[$keyIndex])) { $this->blocktrailPublicKeys[$keyIndex] = BIP32Key::create($pubKey); } } return true; }