Skip to content
This repository was archived by the owner on Oct 24, 2020. It is now read-only.

Commit 5c5d16e

Browse files
committed
Added private keys validation in signing process
1 parent 49671a3 commit 5c5d16e

16 files changed

Lines changed: 590 additions & 296 deletions

lib/BlockCypher/Api/TXSkeleton.php

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use BlockCypher\Common\BlockCypherResourceModel;
66
use BlockCypher\Crypto\PrivateKeyList;
77
use BlockCypher\Crypto\Signer;
8+
use BlockCypher\Exception\BlockCypherSignatureException;
89
use BlockCypher\Rest\ApiContext;
910
use BlockCypher\Transport\BlockCypherRestCall;
1011

@@ -90,30 +91,34 @@ public function sign($privateKeys, $apiContext = null)
9091
// Create PrivateKey objects from plain hex private keys
9192
$privateKeyList = PrivateKeyList::fromHexPrivateKeyArray($privateKeys, $coinSymbol);
9293

93-
$this->generateSignatures($privateKeyList);
94+
$this->generateSignatures($privateKeyList, $coinSymbol);
9495

9596
return $this;
9697
}
9798

9899
/**
99100
* @param PrivateKeyList $privateKeyList
100-
* @throws \Exception
101+
* @param string $coinSymbol
102+
* @throws BlockCypherSignatureException
101103
*/
102-
private function generateSignatures($privateKeyList)
104+
private function generateSignatures($privateKeyList, $coinSymbol)
103105
{
104106
$signatures = array();
105107
$pubkeys = array();
106108
$tosignIndex = 0;
107109

110+
$privateKeysUsed = array();
111+
108112
foreach ($this->getTxInputs() as $txInput) {
109113

110114
// Addresses can be network addresses or pubkeys (multisign txs)
111115
$txInputAddresses = $txInput->getAddresses();
112116

113117
foreach ($txInputAddresses as $inputAddress) {
114-
if ($privateKeyList->keyExists($inputAddress)) {
115118

116-
$privateKey = $privateKeyList->getKey($inputAddress);
119+
if ($privateKeyList->privateKeyExists($inputAddress, $coinSymbol)) {
120+
121+
$privateKey = $privateKeyList->getPrivateKey($inputAddress, $coinSymbol);
117122

118123
// Signature
119124
$hexDataToSign = $this->tosign[$tosignIndex];
@@ -124,15 +129,23 @@ private function generateSignatures($privateKeyList)
124129
$pubKey = $privateKey->getPublicKey()->getHex();
125130
$pubkeys[] = $pubKey;
126131

132+
$privateKeysUsed[] = $inputAddress;
133+
127134
} else {
128135
// User has not provide a private key for this address
129136
// API allows to send partially signed tx
137+
// TODO: add log?
130138
}
131139
}
132140

133141
$tosignIndex++;
134142
}
135143

144+
$numPrivateKeysNotUsed = $privateKeyList->length() - count($privateKeysUsed);
145+
if ($numPrivateKeysNotUsed > 0) {
146+
throw new BlockCypherSignatureException(sprintf("%s private keys do not correspond to any input. Please check private keys provided.", $numPrivateKeysNotUsed));
147+
}
148+
136149
$this->signatures = $signatures;
137150
$this->pubkeys = $pubkeys;
138151
}

lib/BlockCypher/Crypto/PrivateKeyList.php

Lines changed: 105 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace BlockCypher\Crypto;
44

5+
use BitWasp\Bitcoin\Key\PrivateKeyInterface;
56
use BlockCypher\Validation\ArgumentArrayValidator;
67
use BlockCypher\Validation\CoinSymbolValidator;
78

@@ -14,14 +15,11 @@ class PrivateKeyList
1415
/**
1516
* @var PrivateKey[]
1617
*/
17-
private $keys;
18+
private $privateKeys;
1819

19-
/**
20-
* @param PrivateKey[] $keys
21-
*/
22-
function __construct($keys = null)
20+
function __construct()
2321
{
24-
$this->keys = $keys;
22+
$this->privateKeys = array();
2523
}
2624

2725
/**
@@ -34,101 +32,159 @@ public static function fromHexPrivateKeyArray($hexPrivateKeys, $coinSymbol)
3432
ArgumentArrayValidator::validate($hexPrivateKeys, 'hexPrivateKeys');
3533
CoinSymbolValidator::validate($coinSymbol, 'coinSymbol');
3634

37-
$privateKeyList = array();
35+
$privateKeyList = new self($coinSymbol);
36+
3837
foreach ($hexPrivateKeys as $hexPrivateKey) {
3938
$compressed = true;
4039
$privateKey = PrivateKeyManipulator::importPrivateKeyFromHex($hexPrivateKey, $compressed);
4140

42-
// Add private key indexed by address
43-
$address = PrivateKeyManipulator::getAddressFromPrivateKey($privateKey, $coinSymbol);
44-
$privateKeyList[$address] = $privateKey;
45-
4641
// Add private key indexed by public key
47-
$pubKeyHex = $privateKey->getPublicKey()->getHex();
48-
$privateKeyList[$pubKeyHex] = $privateKey;
42+
$privateKeyList->addPrivateKey($privateKey);
4943
}
50-
51-
return new self($privateKeyList);
44+
return $privateKeyList;
5245
}
5346

5447
/**
55-
* Append Key to the list.
48+
* Append private key to the list.
5649
*
57-
* @param PrivateKey $key
58-
* @param string $address
50+
* @param PrivateKeyInterface $privateKey
5951
* @return $this
60-
* @throws \Exception
6152
*/
62-
public function addKey($key, $address = null)
53+
public function addPrivateKey(PrivateKeyInterface $privateKey)
54+
{
55+
$pubKeyHex = $privateKey->getPublicKey()->getHex();
56+
$this->privateKeys[$pubKeyHex] = $privateKey;
57+
}
58+
59+
/**
60+
* @param string $addressOrPublicKey
61+
* @param string $coinSymbol
62+
* @return PrivateKey|null
63+
*/
64+
public function getPrivateKey($addressOrPublicKey, $coinSymbol)
6365
{
64-
if ($address === null) {
65-
$this->keys[] = $key;
66+
$privateKey = $this->getPrivateKeyByPublicKey($addressOrPublicKey);
67+
if ($privateKey !== null) {
68+
return $privateKey;
69+
}
70+
71+
$privateKey = $this->getPrivateKeyByAddress($addressOrPublicKey, $coinSymbol);
72+
if ($privateKey !== null) {
73+
return $privateKey;
74+
}
75+
76+
return null;
77+
}
78+
79+
/**
80+
* @param $publicKeyHex
81+
* @return PrivateKey|null
82+
*/
83+
private function getPrivateKeyByPublicKey($publicKeyHex)
84+
{
85+
if (isset($this->privateKeys[$publicKeyHex])) {
86+
return $this->privateKeys[$publicKeyHex];
6687
} else {
67-
if (isset($this->keys[$address])) {
68-
throw new \Exception("Key $address already in use.");
69-
} else {
70-
$this->keys[$address] = $key;
88+
return null;
89+
}
90+
}
91+
92+
/**
93+
* @param string $addressToFind
94+
* @param $coinSymbol
95+
* @return PrivateKey|null
96+
*/
97+
private function getPrivateKeyByAddress($addressToFind, $coinSymbol)
98+
{
99+
foreach ($this->privateKeys as $privateKey) {
100+
$address = PrivateKeyManipulator::getAddressFromPrivateKey($privateKey, $coinSymbol);
101+
if ($address == $addressToFind) {
102+
return $privateKey;
71103
}
72104
}
105+
return null;
73106
}
74107

75108
/**
76-
* @param string $address
77-
* @throws \Exception
109+
* @param string $addressOrPublicKey
110+
* @param string $coinSymbol
111+
* @return bool
78112
*/
79-
public function deleteKey($address)
113+
public function privateKeyExists($addressOrPublicKey, $coinSymbol)
80114
{
81-
if (isset($this->keys[$address])) {
82-
unset($this->keys[$address]);
83-
} else {
84-
throw new \Exception("Invalid address $address.");
115+
if ($this->privateKeyExistsForPubKey($addressOrPublicKey)) {
116+
return true;
85117
}
118+
119+
if ($this->privateKeyExistsForAddress($addressOrPublicKey, $coinSymbol)) {
120+
return true;
121+
}
122+
123+
return false;
86124
}
87125

88126
/**
89-
* @param string $address
90-
* @return PrivateKey
91-
* @throws \Exception
127+
* @param string $pubKeyHex
128+
* @return bool
92129
*/
93-
public function getKey($address)
130+
private function privateKeyExistsForPubKey($pubKeyHex)
94131
{
95-
if (isset($this->keys[$address])) {
96-
return $this->keys[$address];
132+
if ($this->getPrivateKeyByPublicKey($pubKeyHex) !== null) {
133+
return true;
97134
} else {
98-
throw new \Exception("Address $address not found in PrivateKeyList.");
135+
return false;
99136
}
100137
}
101138

102139
/**
103-
* @param string $address
140+
* @param string $addressToFind
141+
* @param $coinSymbol
104142
* @return bool
105143
*/
106-
public function keyExists($address)
144+
private function privateKeyExistsForAddress($addressToFind, $coinSymbol)
107145
{
108-
return isset($this->keys[$address]);
146+
if ($this->getPrivateKeyByAddress($addressToFind, $coinSymbol) !== null) {
147+
return true;
148+
} else {
149+
return false;
150+
}
109151
}
110152

111153
/**
112154
* @return PrivateKey[]
113155
*/
114-
public function getKeys()
156+
public function getPrivateKeys()
115157
{
116-
return $this->keys;
158+
return $this->privateKeys;
117159
}
118160

119161
/**
120162
* @return string[]
121163
*/
122-
public function addresses()
164+
public function getPublicKeys()
165+
{
166+
return array_keys($this->privateKeys);
167+
}
168+
169+
/**
170+
* @param string $coinSymbol
171+
* @return \string[]
172+
*/
173+
public function getAddresses($coinSymbol)
123174
{
124-
return array_keys($this->keys);
175+
$addresses = array();
176+
foreach ($this->privateKeys as $privateKey) {
177+
$address = PrivateKeyManipulator::getAddressFromPrivateKey($privateKey, $coinSymbol);
178+
$addresses[] = $address;
179+
}
180+
return $addresses;
125181
}
126182

127183
/**
128184
* @return int
129185
*/
130186
public function length()
131187
{
132-
return count($this->keys);
188+
return count($this->privateKeys);
133189
}
134-
}
190+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace BlockCypher\Exception;
4+
5+
/**
6+
* Class BlockCypherSignatureException
7+
*
8+
* @package BlockCypher\Exception
9+
*/
10+
class BlockCypherSignatureException extends \Exception
11+
{
12+
/**
13+
* Default Constructor
14+
*
15+
* @param string|null $message
16+
* @param int $code
17+
*/
18+
public function __construct($message = null, $code = 0)
19+
{
20+
parent::__construct($message, $code);
21+
}
22+
23+
/**
24+
* prints error message
25+
*
26+
* @return string
27+
*/
28+
public function errorMessage()
29+
{
30+
$errorMsg = 'Error on line ' . $this->getLine() . ' in ' . $this->getFile()
31+
. ': <b>' . $this->getMessage() . '</b>';
32+
return $errorMsg;
33+
}
34+
35+
}

sample/address-api/CreateAddressBtcTest3.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,11 @@
77

88
$addressKeyChain = null;
99

10-
// For Sample Purposes Only.
10+
/// For Sample Purposes Only.
1111
$request = null;
1212

13+
// ### Create Address
1314
try {
14-
// ### Create Address
15-
// Create an address by calling the Address::create() method
16-
// with a valid ApiContext (See bootstrap.php for more on `ApiContext`)
1715
$addressKeyChain = \BlockCypher\Api\Address::create(null, $apiContexts['BTC.test3']);
1816
} catch (Exception $ex) {
1917
ResultPrinter::printError("Create Address", "AddressKeyChain", null, $request, $ex);

sample/address-api/GenerateMultisignAddressBtcTest3.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,16 @@
77
require __DIR__ . '/../bootstrap.php';
88

99
$addressKeyChain = new \BlockCypher\Api\AddressKeyChain();
10+
//$pubkeys = array(
11+
// "03798be8162d7d6bc5c4e3b236100fcc0dfee899130f84c97d3a49faf83450fd81",
12+
// "03dd9f1d4a39951013b4305bf61887ada66439ab84a9a2f8aca9dc736041f815f1",
13+
// "03c8e6e99c1d0b42120d5cf40c963e5e8048fd2d2a184758784a041a9d101f1f02"
14+
//);
15+
// BTC-TESTNET: 2NBbY8fbHRLjWXHqRvs8P996N82eTYic1yX
1016
$pubkeys = array(
11-
"03798be8162d7d6bc5c4e3b236100fcc0dfee899130f84c97d3a49faf83450fd81",
12-
"03dd9f1d4a39951013b4305bf61887ada66439ab84a9a2f8aca9dc736041f815f1",
13-
"03c8e6e99c1d0b42120d5cf40c963e5e8048fd2d2a184758784a041a9d101f1f02"
17+
"033e88a5503dc09243e58d9e7a53831c2b77cac014415ad8c29cabab5d933894c1",
18+
"02087f346641256d4ba19cc0473afaa8d3d1b903761b9220a915e1af65a12e613c",
19+
"03051fa1586ff8d509125d3e25308b4c66fcf656b377bf60bfdb296a4898d42efd"
1420
);
1521
$addressKeyChain->setPubkeys($pubkeys);
1622
// script format: 'multisig-n-of-m', where n and m are integers.

0 commit comments

Comments
 (0)