Skip to content

Commit 4fb9856

Browse files
authored
Merge pull request #360 from breadwallet/feature/CORE-720
CORE-720: Add Java-specific BRCoreKey::encryptNative migration function
2 parents 5c108ac + 68a0dfd commit 4fb9856

File tree

12 files changed

+401
-10
lines changed

12 files changed

+401
-10
lines changed

Java/CoreCrypto/src/androidTest/java/com/breadwallet/corecrypto/HelpersAIT.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,21 @@ static void registerCryptoApiProvider() {
9393

9494
// System
9595

96+
/* package */
97+
static System createAndConfigureSystem(File dataDir) {
98+
String storagePath = dataDir.getAbsolutePath();
99+
Account account = HelpersAIT.createDefaultAccount();
100+
SystemListener listener = new DefaultSystemListener() {};
101+
BlockchainDb query = HelpersAIT.createDefaultBlockchainDbWithToken();
102+
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
103+
com.breadwallet.corecrypto.System system = com.breadwallet.corecrypto.System.create(executor, listener, account, false, storagePath, query);
104+
105+
system.configure(Collections.emptyList());
106+
Uninterruptibles.sleepUninterruptibly(5, TimeUnit.SECONDS);
107+
108+
return system;
109+
}
110+
96111
/* package */
97112
static System createAndConfigureSystemWithListener(File dataDir, SystemListener listener) {
98113
String storagePath = dataDir.getAbsolutePath();

Java/CoreCrypto/src/androidTest/java/com/breadwallet/corecrypto/SystemAIT.java

Lines changed: 153 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,18 @@
1212
import org.junit.Test;
1313

1414
import java.io.File;
15-
import java.util.Arrays;
15+
import java.nio.charset.StandardCharsets;
1616
import java.util.Collection;
1717
import java.util.Collections;
1818
import java.util.List;
1919
import java.util.concurrent.TimeUnit;
2020

2121
import com.breadwallet.corecrypto.HelpersAIT.RecordingSystemListener;
2222
import com.breadwallet.crypto.AddressScheme;
23+
import com.breadwallet.crypto.Coder;
24+
import com.breadwallet.crypto.Cipher;
2325
import com.breadwallet.crypto.Currency;
26+
import com.breadwallet.crypto.Key;
2427
import com.breadwallet.crypto.Network;
2528
import com.breadwallet.crypto.System;
2629
import com.breadwallet.crypto.Unit;
@@ -40,6 +43,7 @@
4043
import com.google.common.primitives.UnsignedLong;
4144
import com.google.common.util.concurrent.Uninterruptibles;
4245

46+
import static org.junit.Assert.assertArrayEquals;
4347
import static org.junit.Assert.assertEquals;
4448
import static org.junit.Assert.assertFalse;
4549
import static org.junit.Assert.assertNotEquals;
@@ -105,6 +109,154 @@ public void testSystemAppCurrencies() {
105109
assertEquals("fooi", fooBase.get().getSymbol());
106110
}
107111

112+
@Test
113+
public void testSystemMigrateBRCoreKeyCiphertext() {
114+
// Setup the expected data
115+
116+
Coder coder = Coder.createForAlgorithm(Coder.Algorithm.HEX);
117+
118+
byte[] authenticateData = new byte[0];
119+
120+
Optional<byte[]> maybeNonce12 = coder.decode(
121+
"00000000ed41e4e70e000000");
122+
assertTrue(maybeNonce12.isPresent());
123+
byte[] nonce12 = maybeNonce12.get();
124+
125+
Optional<byte[]> maybeCiphertext = coder.decode(
126+
"1e611714327192ec8454f1e05b1437fa4e56c77ab132d31925c1834f7ed67b7b14d93bdde51b43d38" +
127+
"11b2f22a23ca86287ce130740633f0680207137dabae3faa778c4b45ab692eea527902237ee1cfb9" +
128+
"97217e9df27e8d9131609d2e96745f1dac6c54b180621bacbb00845fe4183c1192d8f45cc267de7e" +
129+
"4ab943dfd73080ae5a3f1dd7c2ea2cc3a009a405154544938c22972744aa62e631c32e9ea7eaa687" +
130+
"ccbc244c6b97d9d69644d4b74805837c5ca3caedd63");
131+
assertTrue(maybeCiphertext.isPresent());
132+
byte[] ciphertext = maybeCiphertext.get();
133+
134+
Optional<byte[]> maybeExpectedPlaintext = coder.decode(
135+
"425a6839314159265359a70ea17800004e5f80400010077ff02c2001003f679c0a200072229ea7a86" +
136+
"41a6d47a81a00f5340d53d348f54f4c2479266a7a9e9a9ea3432d1f1618e7e529ecd56e5203e90c3" +
137+
"48494fd7b98217b4b525b1c41335aee41453c8c121998a4cc2ef5856afa62e6b82358d48acd52866" +
138+
"cc671180b0f2f83aa5c891bb8c043bd254cfd2054b5930bd1910328ea5235866bf4ff8bb9229c284" +
139+
"8538750bc00");
140+
assertTrue(maybeExpectedPlaintext.isPresent());
141+
byte[] expectedPlaintext = maybeExpectedPlaintext.get();
142+
143+
// Test with paper key
144+
145+
{
146+
byte[] pk = "truly flame one position follow sponsor frost oval tuna swallow situate talk".getBytes(StandardCharsets.UTF_8);
147+
Optional<? extends Key> maybeCryptoEncodedKey = Key.createForBIP32ApiAuth(pk, HelpersAIT.BIP39_WORDS_EN);
148+
assertTrue(maybeCryptoEncodedKey.isPresent());
149+
Key key = maybeCryptoEncodedKey.get();
150+
151+
// Migrate using the crypto address params key
152+
153+
Optional<byte[]> maybeMigratedCiphertext = System.migrateBRCoreKeyCiphertext(key, nonce12, authenticateData, ciphertext);
154+
assertTrue(maybeMigratedCiphertext.isPresent());
155+
byte[] migratedCiphertext = maybeMigratedCiphertext.get();
156+
157+
// Decrypt using the crypto address params key
158+
159+
Cipher cipher = Cipher.createForChaCha20Poly1305(key, nonce12, authenticateData);
160+
Optional<byte[]> maybeDecryptedPlaintext = cipher.decrypt(migratedCiphertext);
161+
assertTrue(maybeDecryptedPlaintext.isPresent());
162+
byte[] decryptedPlaintext = maybeDecryptedPlaintext.get();
163+
164+
// Verify correct decryption
165+
166+
assertArrayEquals(expectedPlaintext, decryptedPlaintext);
167+
}
168+
169+
// Test with a private key string encoded using CRYPTO_ADDRESS_PARAMS
170+
171+
byte[] cryptoAddressParamsMigratedCiphertext;
172+
{
173+
// Load the correct key encoded with the crypto address params
174+
175+
byte[] ks = "T7GNuCG4XzHaPGhmUTVGvTnHZodVTrV7KKj1K1vVwTcNcSADqnb5".getBytes(StandardCharsets.UTF_8);
176+
Optional<? extends Key> maybeCryptoEncodedKey = Key.createFromPrivateKeyString(ks);
177+
assertTrue(maybeCryptoEncodedKey.isPresent());
178+
Key key = maybeCryptoEncodedKey.get();
179+
180+
// Migrate using the crypto address params key
181+
182+
Optional<byte[]> maybeMigratedCiphertext = System.migrateBRCoreKeyCiphertext(key, nonce12, authenticateData, ciphertext);
183+
assertTrue(maybeMigratedCiphertext.isPresent());
184+
cryptoAddressParamsMigratedCiphertext = maybeMigratedCiphertext.get();
185+
186+
// Decrypt using the crypto address params key
187+
188+
Cipher cipher = Cipher.createForChaCha20Poly1305(key, nonce12, authenticateData);
189+
Optional<byte[]> maybeDecryptedPlaintext = cipher.decrypt(cryptoAddressParamsMigratedCiphertext);
190+
assertTrue(maybeDecryptedPlaintext.isPresent());
191+
byte[] decryptedPlaintext = maybeDecryptedPlaintext.get();
192+
193+
// Verify correct decryption
194+
195+
assertArrayEquals(expectedPlaintext, decryptedPlaintext);
196+
}
197+
198+
// Test with a private key string encoded using BITCOIN_ADDRESS_PARAMS
199+
200+
byte[] mainnetAddressParamsMigratedCiphertext;
201+
{
202+
// Load the correct key encoded with the bitcoin address params
203+
204+
byte[] ks = "cRo6vMxjZg1EmsYAKEMY5RjyFBHb4DZua9yDZdkTsc6DMHhv8Unr".getBytes(StandardCharsets.UTF_8);
205+
Optional<? extends Key> maybeCryptoEncodedKey = Key.createFromPrivateKeyString(ks);
206+
assertTrue(maybeCryptoEncodedKey.isPresent());
207+
Key key = maybeCryptoEncodedKey.get();
208+
209+
// Migrate using the crypto address params key
210+
211+
Optional<byte[]> maybeMigratedCiphertext = System.migrateBRCoreKeyCiphertext(key, nonce12, authenticateData, ciphertext);
212+
assertTrue(maybeMigratedCiphertext.isPresent());
213+
mainnetAddressParamsMigratedCiphertext = maybeMigratedCiphertext.get();
214+
215+
// Decrypt using the crypto address params key
216+
217+
Cipher cipher = Cipher.createForChaCha20Poly1305(key, nonce12, authenticateData);
218+
Optional<byte[]> maybeDecryptedPlaintext = cipher.decrypt(mainnetAddressParamsMigratedCiphertext);
219+
assertTrue(maybeDecryptedPlaintext.isPresent());
220+
byte[] decryptedPlaintext = maybeDecryptedPlaintext.get();
221+
222+
// Verify correct decryption
223+
224+
assertArrayEquals(expectedPlaintext, decryptedPlaintext);
225+
}
226+
227+
// Confirm that migrated ciphertext is the same, regardless of private key encoding
228+
229+
assertArrayEquals(cryptoAddressParamsMigratedCiphertext, mainnetAddressParamsMigratedCiphertext);
230+
231+
{
232+
// Load the incorrect uncompressed key
233+
234+
byte[] ks = "5Kb8kLf9zgWQnogidDA76MzPL6TsZZY36hWXMssSzNydYXYB9KF".getBytes(StandardCharsets.UTF_8);
235+
Optional<? extends Key> maybeCryptoEncodedKey = Key.createFromPrivateKeyString(ks);
236+
assertTrue(maybeCryptoEncodedKey.isPresent());
237+
Key key = maybeCryptoEncodedKey.get();
238+
239+
// Migrate and fail
240+
241+
Optional<byte[]> maybeMigratedCiphertext = System.migrateBRCoreKeyCiphertext(key, nonce12, authenticateData, ciphertext);
242+
assertFalse(maybeMigratedCiphertext.isPresent());
243+
}
244+
245+
{
246+
// Load the incorrect uncompressed key
247+
248+
byte[] ks = "KyvGbxRUoofdw3TNydWn2Z78dBHSy2odn1d3wXWN2o3SAtccFNJL".getBytes(StandardCharsets.UTF_8);
249+
Optional<? extends Key> maybeCryptoEncodedKey = Key.createFromPrivateKeyString(ks);
250+
assertTrue(maybeCryptoEncodedKey.isPresent());
251+
Key key = maybeCryptoEncodedKey.get();
252+
253+
// Migrate and fail
254+
255+
Optional<byte[]> maybeMigratedCiphertext = System.migrateBRCoreKeyCiphertext(key, nonce12, authenticateData, ciphertext);
256+
assertFalse(maybeMigratedCiphertext.isPresent());
257+
}
258+
}
259+
108260
@Test
109261
public void testSystemBtc() {
110262
testSystemForCurrency("btc", WalletManagerMode.API_ONLY, AddressScheme.BTC_LEGACY);

Java/CoreCrypto/src/main/java/com/breadwallet/corecrypto/Cipher.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@
1616
/* package */
1717
final class Cipher implements com.breadwallet.crypto.Cipher {
1818

19+
/* package */
20+
static Optional<byte[]> migrateBRCoreKeyCiphertext(com.breadwallet.crypto.Key key, byte[] nonce12, byte[] authenticatedData,
21+
byte[] ciphertext) {
22+
return Cipher.createForChaCha20Poly1305(key, nonce12, authenticatedData).migrateBRCoreKeyCiphertext(ciphertext);
23+
}
24+
1925
/* package */
2026
static Cipher createForAesEcb(byte[] key) {
2127
BRCryptoCipher cipher = BRCryptoCipher.createAesEcb(key).orNull();
@@ -24,11 +30,11 @@ static Cipher createForAesEcb(byte[] key) {
2430
}
2531

2632
/* package */
27-
static Cipher createForChaCha20Poly1305(com.breadwallet.crypto.Key key, byte[] nonce12, byte[] ad) {
33+
static Cipher createForChaCha20Poly1305(com.breadwallet.crypto.Key key, byte[] nonce12, byte[] authenticatedData) {
2834
BRCryptoCipher cipher = BRCryptoCipher.createChaCha20Poly1305(
2935
Key.from(key).getBRCryptoKey(),
3036
nonce12,
31-
ad)
37+
authenticatedData)
3238
.orNull();
3339
checkNotNull(cipher);
3440
return Cipher.create(cipher);
@@ -68,4 +74,8 @@ public Optional<byte[]> encrypt(byte[] data) {
6874
public Optional<byte[]> decrypt(byte[] data) {
6975
return core.decrypt(data);
7076
}
77+
78+
private Optional<byte[]> migrateBRCoreKeyCiphertext(byte[] data) {
79+
return core.migrateBRCoreKeyCiphertext(data);
80+
}
7181
}

Java/CoreCrypto/src/main/java/com/breadwallet/corecrypto/CryptoApiProvider.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ public Optional<Currency> asBDBCurrency(String uids, String name, String code, S
8383
return System.asBDBCurrency(uids, name, code, type, decimals);
8484
}
8585

86+
@Override
87+
public Optional<byte[]> migrateBRCoreKeyCiphertext(com.breadwallet.crypto.Key key, byte[] nonce12,
88+
byte[] authenticatedData, byte[] ciphertext) {
89+
return System.migrateBRCoreKeyCiphertext(key, nonce12, authenticatedData, ciphertext);
90+
}
91+
8692
@Override
8793
public void wipe(com.breadwallet.crypto.System system) {
8894
System.wipe(system);

Java/CoreCrypto/src/main/java/com/breadwallet/corecrypto/System.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,14 @@ static Optional<com.breadwallet.crypto.blockchaindb.models.bdb.Currency> asBDBCu
252252
);
253253
}
254254

255+
/* package */
256+
static Optional<byte[]> migrateBRCoreKeyCiphertext(com.breadwallet.crypto.Key key,
257+
byte[] nonce12,
258+
byte[] authenticatedData,
259+
byte[] ciphertext) {
260+
return Cipher.migrateBRCoreKeyCiphertext(key, nonce12, authenticatedData, ciphertext);
261+
}
262+
255263
/* package */
256264
static void wipe(com.breadwallet.crypto.System system) {
257265
// Safe the path to the persistent storage

Java/CoreNative/src/main/java/com/breadwallet/corenative/CryptoLibraryDirect.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,7 @@ public static native void cwmAnnounceGetTokensItem(Pointer cwm, Pointer callback
363363
public static native int cryptoCipherEncrypt(Pointer cipher, byte[] dst, SizeT dstLen, byte[] src, SizeT srcLen);
364364
public static native SizeT cryptoCipherDecryptLength(Pointer cipher, byte[] src, SizeT srcLen);
365365
public static native int cryptoCipherDecrypt(Pointer cipher, byte[] dst, SizeT dstLen, byte[] src, SizeT srcLen);
366+
public static native int cryptoCipherMigrateBRCoreKeyCiphertext(Pointer cipher, byte[] dst, SizeT dstLen, byte[] src, SizeT srcLen);
366367
public static native void cryptoCipherGive(Pointer cipher);
367368

368369
// crypto/BRCryptoCoder.h

Java/CoreNative/src/main/java/com/breadwallet/corenative/crypto/BRCryptoCipher.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,17 @@ public Optional<byte[]> decrypt(byte[] input) {
7575
return result == BRCryptoBoolean.CRYPTO_TRUE ? Optional.of(output) : Optional.absent();
7676
}
7777

78+
public Optional<byte[]> migrateBRCoreKeyCiphertext(byte[] input) {
79+
Pointer thisPtr = this.getPointer();
80+
81+
int lengthAsInt = input.length;
82+
if (0 == lengthAsInt) return Optional.absent();
83+
84+
byte[] output = new byte[lengthAsInt];
85+
int result = CryptoLibraryDirect.cryptoCipherMigrateBRCoreKeyCiphertext(thisPtr, output, new SizeT(output.length), input, new SizeT(input.length));
86+
return result == BRCryptoBoolean.CRYPTO_TRUE ? Optional.of(output) : Optional.absent();
87+
}
88+
7889
public void give() {
7990
Pointer thisPtr = this.getPointer();
8091

Java/Crypto/src/main/java/com/breadwallet/crypto/CryptoApi.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public interface AmountProvider {
3737
public interface SystemProvider {
3838
System create(ScheduledExecutorService executor, SystemListener listener, Account account, boolean isMainnet, String path, BlockchainDb query);
3939
Optional<Currency> asBDBCurrency(String uids, String name, String code, String type, UnsignedInteger decimals);
40+
Optional<byte[]> migrateBRCoreKeyCiphertext(Key key, byte[] nonce12, byte[] authenticatedData, byte[] ciphertext);
4041
void wipe(System system);
4142
void wipeAll(String path, List<System> exemptSystems);
4243
}

Java/Crypto/src/main/java/com/breadwallet/crypto/System.java

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,26 @@ static Optional<com.breadwallet.crypto.blockchaindb.models.bdb.Currency> asBlock
6464

6565
}
6666

67+
/**
68+
* Re-encrypt ciphertext blobs that were encrypted using the BRCoreKey::encryptNative routine.
69+
*
70+
* The ciphertext will be decrypted using the previous decryption routine. The plaintext from that
71+
* operation will be encrypted using the current {@link Cipher#encrypt(byte[])} routine with a
72+
* {@link Cipher#createForChaCha20Poly1305(Key, byte[], byte[])} cipher. That updated ciphertext
73+
* is then returned and should be used to immediately overwrite the old ciphertext blob so that
74+
* it can be properly decrypted using the {@link Cipher#decrypt(byte[])} routine going forward.
75+
*
76+
* @param key The cipher key
77+
* @param nonce12 The 12 byte nonce data
78+
* @param authenticatedData The authenticated data
79+
* @param ciphertext The ciphertext to update the encryption on
80+
*
81+
* @return The updated ciphertext, if decryption and re-encryption succeeds; absent otherwise.
82+
*/
83+
static Optional<byte[]> migrateBRCoreKeyCiphertext(Key key, byte[] nonce12, byte[] authenticatedData, byte[] ciphertext) {
84+
return CryptoApi.getProvider().systemProvider().migrateBRCoreKeyCiphertext(key, nonce12, authenticatedData, ciphertext);
85+
}
86+
6787
/**
6888
* Cease use of `system` and remove (aka 'wipe') its persistent storage.
6989
*
@@ -185,8 +205,23 @@ boolean createWalletManager(Network network,
185205

186206
boolean supportsWalletManagerMode(Network network, WalletManagerMode mode);
187207

208+
/**
209+
* If migration is required, return the currency code; otherwise, return nil.
210+
*
211+
* Note: it is not an error not to migrate.
212+
*/
213+
boolean migrateRequired(Network network);
214+
215+
/**
216+
* Migrate the storage for a network given transaction, block and peer blobs.
217+
*
218+
* Support for persistent storage migration to allow prior App versions to migrate their SQLite
219+
* database representations of BTC/BTC transations, blocks and peers into 'Generic Crypto' - where
220+
* these entities are persistently
221+
*
222+
* The provided blobs must be consistent with `network`. For exmaple, if `network` represents BTC or BCH
223+
* then the blobs must be of type {@link TransactionBlob.Btc}; otherwise a MigrateError is thrown.
224+
*/
188225
void migrateStorage (Network network, List<TransactionBlob> transactionBlobs, List<BlockBlob> blockBlobs,
189226
List<PeerBlob> peerBlobs) throws MigrateError;
190-
191-
boolean migrateRequired(Network network);
192227
}

Swift/BRCrypto/common/BRCryptoCipher.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public final class CoreCipher: Cipher {
8080

8181
public func decrypt (data source: Data) -> Data? {
8282
return source.withUnsafeBytes { (sourceBytes: UnsafeRawBufferPointer) -> Data? in
83-
let sourceAddr = sourceBytes.baseAddress?.assumingMemoryBound(to: Int8.self)
83+
let sourceAddr = sourceBytes.baseAddress?.assumingMemoryBound(to: UInt8.self)
8484
let sourceCount = sourceBytes.count
8585

8686
let targetCount = cryptoCipherDecryptLength(self.core, sourceAddr, sourceCount)

0 commit comments

Comments
 (0)