Skip to content

Commit 065a8f2

Browse files
author
Chris Kleeschulte
committed
Creating BitPay client with URI of key
- Added constructors for passing the URI of your private key. We prefer to have users of this library pass in a URI for their key file. That way, this api isn't just writing new key files to the current working directory. If the URI does not have a key already, one will be created there (if writeable). It will be the user's responsibility to safeguard their private keys. - A bit of code cleanup in terms of spelling and removal of warnings, etc.
1 parent 87bd21f commit 065a8f2

File tree

5 files changed

+144
-71
lines changed

5 files changed

+144
-71
lines changed

GUIDE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Each client paired with the BitPay server requires a public and private key. Th
1515

1616
The private key should be stored in the client environment such that it cannot be compromised. If your private key is compromised you should revoke the compromised client identity from the BitPay server and re-pair your client, see the [API tokens](https://bitpay.com/api-tokens) for more information.
1717

18-
This SDK provides the capability of internally storing the private key on the client local file system. If the local file system is secure then this is a good option. It is also possible to generate the key yourself (using the SDK) and store the key as required. It is not recommended to transmit the private key over any public or unsecure networks.
18+
This SDK provides the capability of internally storing the private key on the client local file system. If the local file system is secure then this is a good option. It is also possible to generate the key yourself (using the SDK) and store the key as required. It is not recommended to transmit the private key over any public or unsecured networks.
1919

2020
```java
2121
// Let the SDK store the private key on the clients local file system.
@@ -88,7 +88,7 @@ String status = invoice.getStatus();
8888

8989
### Create an invoice (extended)
9090

91-
You can add optional attributes to the invoice. Atributes that are not set are ignored or given default values. For example:
91+
You can add optional attributes to the invoice. Attributes that are not set are ignored or given default values. For example:
9292

9393
```java
9494
InvoiceBuyer buyer = new InvoiceBuyer();

src/main/java/controller/BitPay.java

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ public class BitPay {
5555
* @param envUrl The target server URL.
5656
* @throws BitPayException
5757
*/
58+
@Deprecated
5859
public BitPay(String clientName, String envUrl) throws BitPayException
5960
{
6061
if (clientName.equals(BITPAY_PLUGIN_INFO)) {
@@ -80,6 +81,9 @@ public BitPay(String clientName, String envUrl) throws BitPayException
8081
this.initKeys();
8182
} catch (IOException e) {
8283
throw new BitPayException("Error: failed to intialize public/private key pair\n" + e.getMessage());
84+
} catch (URISyntaxException e) {
85+
e.printStackTrace();
86+
throw new BitPayException("Error: " + e.getMessage());
8387
}
8488

8589
this.deriveIdentity();
@@ -91,6 +95,7 @@ public BitPay(String clientName, String envUrl) throws BitPayException
9195
* @param clientName The label for this client.
9296
* @throws BitPayException
9397
*/
98+
@Deprecated
9499
public BitPay(String clientName) throws BitPayException
95100
{
96101
this(clientName, BITPAY_URL);
@@ -100,11 +105,42 @@ public BitPay(String clientName) throws BitPayException
100105
* Constructor for use if the keys and SIN are managed by this library. Use BitPay production server. Default client name.
101106
* @throws BitPayException
102107
*/
108+
@Deprecated
103109
public BitPay() throws BitPayException
104110
{
105111
this(BITPAY_PLUGIN_INFO, BITPAY_URL);
106112
}
107113

114+
/**
115+
* Constructor for use if the keys derived external to this library. Use BitPay production server. Default client name.
116+
* @param privateKey A URI object representing the compressed, DER-encoded ASN.1 private key
117+
* @throws BitPayException, IOException
118+
*/
119+
public BitPay(URI privateKey) throws BitPayException, URISyntaxException, IOException {
120+
this(KeyUtils.loadEcKey(privateKey), BITPAY_PLUGIN_INFO, BITPAY_URL);
121+
}
122+
123+
/**
124+
* Constructor for use if the keys derived external to this library. Use BitPay production server. Default client name.
125+
* @param privateKey A URI object representing the compressed, DER-encoded ASN.1 private key
126+
* @param clientName The label for this client.
127+
* @throws BitPayException, IOException
128+
*/
129+
public BitPay(URI privateKey, String clientName) throws BitPayException, IOException, URISyntaxException {
130+
this(KeyUtils.loadEcKey(privateKey), clientName, BITPAY_URL);
131+
}
132+
133+
/**
134+
* Constructor for use if the keys derived external to this library. Use BitPay production server. Default client name.
135+
* @param privateKey A URI object representing the compressed, DER-encoded ASN.1 private key
136+
* @param clientName The label for this client.
137+
* @param envUrl The target server URL.
138+
* @throws BitPayException, IOException
139+
*/
140+
public BitPay(URI privateKey, String clientName, String envUrl) throws BitPayException, IOException, URISyntaxException {
141+
this(KeyUtils.loadEcKey(privateKey), clientName, envUrl);
142+
}
143+
108144
/**
109145
* Constructor for use if the keys and SIN were derived external to this library.
110146
* @param ecKey An elliptical curve key.
@@ -262,7 +298,6 @@ public String requestClientAuthorization(String facade) throws BitPayException
262298
* Test whether this client is authorized for a specified level of API access.
263299
* @param facade Defines the level of API access being requested.
264300
* @return True if this client is authorized, false otherwise.
265-
* @throws BitPayException
266301
*/
267302
public boolean clientIsAuthorized(String facade)
268303
{
@@ -273,9 +308,8 @@ public boolean clientIsAuthorized(String facade)
273308
* Retrieve a token associated with a known resource. The token is used to access other related resources.
274309
* @param id The identifier for the desired resource.
275310
* @return The token associated with resource.
276-
* @throws BitPayException
277311
*/
278-
public String getAccessToken(String id) throws BitPayException
312+
public String getAccessToken(String id)
279313
{
280314
return _tokenCache.get(id);
281315
}
@@ -573,8 +607,7 @@ public Rates getRates() throws BitPayException
573607
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
574608
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
575609

576-
private void initKeys() throws IOException
577-
{
610+
private void initKeys() throws IOException, URISyntaxException {
578611
if (KeyUtils.privateKeyExists()) {
579612
_ecKey = KeyUtils.loadEcKey();
580613

@@ -666,9 +699,7 @@ private int getAccessTokens() throws BitPayException
666699

667700
private Hashtable<String, String> getParams()
668701
{
669-
Hashtable<String, String> params = new Hashtable<String, String>();
670-
671-
return params;
702+
return new Hashtable<String, String>();
672703
}
673704

674705
private HttpResponse get(String uri, Hashtable<String, String> parameters) throws BitPayException
@@ -718,7 +749,7 @@ private HttpResponse post(String uri, String json, boolean signatureRequired) th
718749
try {
719750
HttpPost post = new HttpPost(_baseUrl + uri);
720751

721-
post.setEntity(new ByteArrayEntity(json.toString().getBytes("UTF8")));
752+
post.setEntity(new ByteArrayEntity(json.getBytes("UTF8")));
722753

723754
if (signatureRequired) {
724755
String signature = KeyUtils.sign(_ecKey, _baseUrl + uri + json);

src/main/java/controller/KeyUtils.java

Lines changed: 65 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -7,64 +7,77 @@
77

88
import java.io.*;
99
import java.math.BigInteger;
10+
import java.net.URI;
11+
import java.net.URISyntaxException;
1012
import java.security.SecureRandom;
1113

1214
public class KeyUtils {
1315

1416
final private static char[] hexArray = "0123456789abcdef".toCharArray();
1517
final private static String PRIV_KEY_FILENAME = "bitpay_private.key";
18+
private static URI privateKey;
1619

17-
public KeyUtils() {}
20+
public KeyUtils() {
21+
}
1822

19-
public static boolean privateKeyExists()
20-
{
23+
public static boolean privateKeyExists() {
2124
return new File(PRIV_KEY_FILENAME).exists();
2225
}
2326

24-
public static ECKey createEcKey()
25-
{
27+
public static ECKey createEcKey() {
2628
//Default constructor uses SecureRandom numbers.
2729
return new ECKey();
2830
}
2931

30-
public static ECKey createEcKeyFromHexString(String privateKey)
31-
{
32+
public static ECKey createEcKeyFromHexString(String privateKey) {
3233
//if you are going to choose this option, please ensure this string is as random as
3334
//possible, consider http://world.std.com/~reinhold/diceware.html
3435
SecureRandom randomSeed = new SecureRandom(privateKey.getBytes());
35-
ECKey key = new ECKey(randomSeed);
36-
37-
return key;
36+
return new ECKey(randomSeed);
3837
}
3938

4039
/**
41-
* Convenience method.
40+
* Convenience method.
4241
*/
43-
public static ECKey createEcKeyFromHexStringFile(String privKeyFile) throws IOException
44-
{
45-
String privateKey = getKeyStringFromFile(privKeyFile);
46-
47-
return createEcKeyFromHexString(privateKey);
42+
public static ECKey createEcKeyFromHexStringFile(String privKeyFile) throws IOException {
43+
return createEcKeyFromHexString(getKeyStringFromFile(privKeyFile));
4844
}
4945

50-
public static ECKey loadEcKey() throws IOException
51-
{
52-
FileInputStream fileInputStream = null;
53-
File file = new File(PRIV_KEY_FILENAME);
46+
public static ECKey loadEcKey() throws IOException {
47+
FileInputStream fileInputStream;
48+
File file;
49+
50+
if (KeyUtils.privateKey == null) {
51+
file = new File(PRIV_KEY_FILENAME);
52+
} else {
53+
file = new File(KeyUtils.privateKey);
54+
}
5455

5556
byte[] bytes = new byte[(int) file.length()];
5657

5758
fileInputStream = new FileInputStream(file);
58-
fileInputStream.read(bytes);
59+
int numBytesRead = fileInputStream.read(bytes);
60+
5961
fileInputStream.close();
6062

61-
ECKey key = ECKey.fromASN1(bytes);
63+
if (numBytesRead == -1) {
64+
throw new IOException("read nothing from the file.");
65+
}
66+
return ECKey.fromASN1(bytes);
67+
}
6268

63-
return key;
69+
public static ECKey loadEcKey(URI privateKey) throws IOException, URISyntaxException {
70+
KeyUtils.privateKey = privateKey;
71+
File file = new File(privateKey);
72+
if (!file.exists()) {
73+
ECKey key = createEcKey();
74+
saveEcKey(key, KeyUtils.privateKey);
75+
return key;
76+
}
77+
return loadEcKey();
6478
}
6579

66-
public static String getKeyStringFromFile(String filename) throws IOException
67-
{
80+
public static String getKeyStringFromFile(String filename) throws IOException {
6881
BufferedReader br;
6982

7083
br = new BufferedReader(new FileReader(filename));
@@ -76,18 +89,35 @@ public static String getKeyStringFromFile(String filename) throws IOException
7689
return line;
7790
}
7891

79-
public static void saveEcKey(ECKey ecKey) throws IOException
80-
{
92+
public static void saveEcKey(ECKey ecKey) throws IOException {
8193
byte[] bytes = ecKey.toASN1();
94+
File file;
8295

83-
FileOutputStream output = new FileOutputStream(new File(PRIV_KEY_FILENAME));
96+
if (KeyUtils.privateKey == null) {
97+
file = new File(PRIV_KEY_FILENAME);
98+
} else {
99+
file = new File(KeyUtils.privateKey);
100+
}
101+
102+
FileOutputStream output = new FileOutputStream(file);
84103

85104
output.write(bytes);
86105
output.close();
87106
}
88107

89-
public static String deriveSIN(ECKey ecKey) throws IllegalArgumentException
90-
{
108+
public static void saveEcKey(ECKey ecKey, URI privateKey) throws IOException, URISyntaxException {
109+
File file = new File(privateKey);
110+
//we shan't overwrite an existing file
111+
112+
if (file.exists()) {
113+
return;
114+
}
115+
KeyUtils.privateKey = privateKey;
116+
saveEcKey(ecKey);
117+
}
118+
119+
120+
public static String deriveSIN(ECKey ecKey) throws IllegalArgumentException {
91121
// Get sha256 hash and then the RIPEMD-160 hash of the public key (this call gets the result in one step).
92122
byte[] pubKeyHash = ecKey.getPubKeyHash();
93123

@@ -110,9 +140,7 @@ public static String deriveSIN(ECKey ecKey) throws IllegalArgumentException
110140
// Append first four bytes to fully appended SIN string
111141
String unencoded = preSIN + first4Bytes;
112142
byte[] unencodedBytes = new BigInteger(unencoded, 16).toByteArray();
113-
String encoded = Base58.encode(unencodedBytes);
114-
115-
return encoded;
143+
return Base58.encode(unencodedBytes);
116144
}
117145

118146
public static String sign(ECKey key, String input) throws UnsupportedEncodingException {
@@ -126,14 +154,12 @@ public static String sign(ECKey key, String input) throws UnsupportedEncodingExc
126154
return bytesToHex(bytes);
127155
}
128156

129-
private static int getHexVal(char hex)
130-
{
131-
int val = (int)hex;
157+
private static int getHexVal(char hex) {
158+
int val = (int) hex;
132159
return val - (val < 58 ? 48 : (val < 97 ? 55 : 87));
133160
}
134161

135-
public static byte[] hexToBytes(String hex) throws IllegalArgumentException
136-
{
162+
public static byte[] hexToBytes(String hex) throws IllegalArgumentException {
137163
char[] hexArray = hex.toCharArray();
138164

139165
if (hex.length() % 2 == 1) {
@@ -143,7 +169,7 @@ public static byte[] hexToBytes(String hex) throws IllegalArgumentException
143169
byte[] arr = new byte[hex.length() >> 1];
144170

145171
for (int i = 0; i < hex.length() >> 1; ++i) {
146-
arr[i] = (byte)((getHexVal(hexArray[i << 1]) << 4) + (getHexVal(hexArray[(i << 1) + 1])));
172+
arr[i] = (byte) ((getHexVal(hexArray[i << 1]) << 4) + (getHexVal(hexArray[(i << 1) + 1])));
147173
}
148174

149175
return arr;

0 commit comments

Comments
 (0)