public function recurse_until_unique_bip32_key($bip32_key_row) { $this->load->model('used_pubkeys_model'); // Loop until a unique key is found. $valid = FALSE; while ($valid == FALSE) { $new_key = \BitWasp\BitcoinLib\BIP32::build_key($bip32_key_row['key'], $bip32_key_row['key_index']); $public_key = \BitWasp\BitcoinLib\BIP32::extract_public_key($new_key); // Check that when previously used keys are removed, result is still one (never used before) if (count($this->used_pubkeys_model->remove_used_keys(array($public_key))) == 1) { $valid = TRUE; } else { $bip32_key_row['key_index']++; } } return array('parent_extended_public_key' => $bip32_key_row['key'], 'provider' => isset($bip32_key_row['provider']) ? $bip32_key_row['provider'] : 'Manual', 'extended_public_key' => $new_key[0], 'public_key' => $public_key, 'key_index' => $bip32_key_row['key_index']); }
/** * 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]; }
public function testBIP32() { $masterkey = "tpubD9q6vq9zdP3gbhpjs7n2TRvT7h4PeBhxg1Kv9jEc1XAss7429VenxvQTsJaZhzTk54gnsHRpgeeNMbm1QTag4Wf1QpQ3gy221GDuUCxgfeZ"; $import = BIP32::import($masterkey); $this->assertEquals("022f6b9339309e89efb41ecabae60e1d40b7809596c68c03b05deb5a694e33cd26", $import['key']); $this->assertEquals("tpubDAtJthHcm9MJwmHp4r2UwSTmiDYZWHbQUMqySJ1koGxQpRNSaJdyL2Ab8wwtMm5DsMMk3v68299LQE6KhT8XPQWzxPLK5TbTHKtnrmjV8Gg", BIP32::build_key($masterkey, "0")[0]); $this->assertEquals("tpubDDfqpEKGqEVa5FbdLtwezc6Xgn81teTFFVA69ZfJBHp4UYmUmhqVZMmqXeJBDahvySZrPjpwMy4gKfNfrxuFHmzo1r6srB4MrsDKWbwEw3d", BIP32::build_key($masterkey, "0/0")[0]); $this->assertEquals("tpubDHNy3kAG39ThyiwwsgoKY4iRenXDRtce8qdCFJZXPMCJg5dsCUHayp84raLTpvyiNA9sXPob5rgqkKvkN8S7MMyXbnEhGJMW64Cf4vFAoaF", BIP32::build_key(BIP32::master_key("000102030405060708090a0b0c0d0e0f", "bitcoin", true), "M/0'/1/2'/2/1000000000")[0]); }
<?php use BitWasp\BitcoinLib\BIP32; use BitWasp\BitcoinLib\BitcoinLib; use BitWasp\BitcoinLib\RawTransaction; 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
public function testChildKeyDerivationOne() { $test_vectors = [0 => ['master' => '000102030405060708090a0b0c0d0e0f', 'ckd' => [0 => ['child' => "0'", 'priv' => 'xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7', 'pub' => 'xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw'], 1 => ['child' => '1', 'priv' => 'xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs', 'pub' => 'xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ'], 2 => ['child' => "2'", 'priv' => 'xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM', 'pub' => 'xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5'], 3 => ['child' => '2', 'priv' => 'xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334', 'pub' => 'xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV'], 4 => ['child' => '1000000000', 'priv' => 'xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76', 'pub' => 'xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy']]], 1 => ['master' => 'fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542', 'ckd' => [0 => ['child' => "0", 'priv' => 'xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt', 'pub' => 'xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH'], 1 => ['child' => "2147483647'", 'priv' => 'xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9', 'pub' => 'xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a'], 2 => ['child' => "1", 'priv' => 'xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef', 'pub' => 'xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon'], 3 => ['child' => "2147483646'", 'priv' => 'xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc', 'pub' => 'xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL'], 4 => ['child' => '2', 'priv' => 'xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j', 'pub' => 'xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt']]]]; foreach ($test_vectors as $test => $vector) { $master = BIP32::master_key($vector['master']); $key = $master; foreach ($vector['ckd'] as $test_array) { $key = BIP32::build_key($key, $test_array['child']); $this->assertEquals($key[0], $test_array['priv']); $pub = BIP32::extended_private_to_public($key); $this->assertEquals($pub[0], $test_array['pub']); } } }
/** * build child key * * @param string|BIP32Path $path * @return BIP32Key * @throws \Exception */ public function buildKey($path) { if (!isset($this->derivations[(string) $path])) { $key = BIP32::build_key($this->tuple(), (string) $path); $this->derivations[(string) $path] = $key; } return new BIP32Key($this->derivations[(string) $path]); }
<?php use BitWasp\BitcoinLib\BIP32; require_once __DIR__ . '/../vendor/autoload.php'; // Load a 128 bit key, and convert this to extended key format. $master = BIP32::master_key('41414141414141414141414141414141414141'); $def = "0'"; echo "\nMaster key\n m : {$master[0]} \n"; // Define what derivation you wish to calculate. $key = BIP32::build_key($master, $def); // Build the extended key // 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"; $nextpub = BIP32::build_key($pub, '0'); echo "Child key\n"; echo " {$nextpub[1]} : {$nextpub[0]}\n";
/** * 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; }
$wallet[1] = BIP32::master_key('b861e093a58718e145b9791af35fb222'); $wallet[2] = BIP32::master_key('b861e093a58718e145b9791af35fb333'); print_r($wallet); echo "Now we will generate a m/0' extended key. These will yield a private key\n"; $user[0] = BIP32::build_key($wallet[0][0], "3'"); $user[1] = BIP32::build_key($wallet[1][0], "23'"); $user[2] = BIP32::build_key($wallet[2][0], "9'"); print_r($user); // As the previous is a private key, we should convert to the corresponding // public key: M/0' echo "As the previous is a private key, we should convert it to the corresponding\n"; echo "public key: M/0' \n"; $pub[0] = BIP32::extended_private_to_public($user[0]); $pub[1] = BIP32::extended_private_to_public($user[1]); $pub[2] = BIP32::extended_private_to_public($user[2]); print_r($pub); echo "This is the key you will ask your users for. For repeated transactions\n"; echo "BIP32 allows you to deterministically generate public keys, meaning less\n"; echo "effort for everyone involved\n\n"; echo "Now we can generate many multisignature addresses from what we have here: \n"; for ($i = 0; $i < 3; $i++) { $bip32key[0] = BIP32::build_key($pub[0], "0/{$i}"); $bip32key[1] = BIP32::build_key($pub[1], "0/{$i}"); $bip32key[2] = BIP32::build_key($pub[2], "0/{$i}"); print_r($bip32key); $pubkey[0] = BIP32::extract_public_key($bip32key[0]); $pubkey[1] = BIP32::extract_public_key($bip32key[1]); $pubkey[2] = BIP32::extract_public_key($bip32key[2]); print_r($pubkey); print_r(RawTransaction::create_multisig(2, $pubkey)); }
echo "test two\n"; $master = BIP32::master_key('fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542'); echo "Chain m\n"; echo " ext priv:\n " . $master[0] . "\n"; $public = BIP32::extended_private_to_public($master); echo " ext pub:\n " . $public[0] . "\n"; echo "Chain m/0\n"; $key = BIP32::build_key($master, '0'); echo " ext priv:\n " . $key[0] . "\n"; $public = BIP32::extended_private_to_public($key); echo " ext pub: \n " . $public[0] . "\n"; echo "Chain m/0/2147483647'\n"; $key = BIP32::build_key($key, "2147483647'"); echo " ext priv:\n " . $key[0] . "\n"; $public = BIP32::extended_private_to_public($key); echo " ext pub: \n " . $public[0] . "\n"; echo "Chain m/0/2147483647'/1\n"; $key = BIP32::build_key($key, "1"); echo " ext priv:\n " . $key[0] . "\n"; $public = BIP32::extended_private_to_public($key); echo " ext pub: \n " . $public[0] . "\n"; echo "Chain m/0/2147483647'/1/2147483646'\n"; $key = BIP32::build_key($key, "2147483646'"); echo " ext priv:\n " . $key[0] . "\n"; $public = BIP32::extended_private_to_public($key); echo " ext pub: \n " . $public[0] . "\n"; echo "Chain m/0/2147483647'/1/2147483646'/2\n"; $key = BIP32::build_key($key, "2"); echo " ext priv:\n " . $key[0] . "\n"; $public = BIP32::extended_private_to_public($key); echo " ext pub: \n " . $public[0] . "\n";