Browse Source

Merge branch 'master' into patch-1

singleaddress
Overtorment 5 years ago
committed by GitHub
parent
commit
41d44093ab
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 26
      BlueComponents.js
  2. 2
      BlueElectrum.js
  3. 4
      MainBottomTabs.js
  4. 5
      class/abstract-hd-wallet.js
  5. 23
      class/hd-legacy-breadwallet-wallet.js
  6. 28
      class/hd-legacy-p2pkh-wallet.js
  7. 2
      class/hd-segwit-bech32-transaction.js
  8. 29
      class/hd-segwit-bech32-wallet.js
  9. 10
      class/hd-segwit-p2sh-wallet.js
  10. 6
      class/legacy-wallet.js
  11. 18
      class/segwit-bech-wallet.js
  12. 27
      class/segwit-p2sh-wallet.js
  13. 4
      ios/BlueWallet.xcodeproj/project.pbxproj
  14. 20
      ios/BlueWallet/Base.lproj/LaunchScreen.xib
  15. 16
      ios/Podfile.lock
  16. 236
      loc/ZAR_Afr.js
  17. 239
      loc/ZAR_Xho.js
  18. 7
      loc/index.js
  19. 4
      loc/th_TH.js
  20. 235
      loc/zh_tw.js
  21. 168
      models/signer.js
  22. 63
      package-lock.json
  23. 10
      package.json
  24. 12
      patches/transaction.js.patch
  25. 12
      patches/transaction_builder.js.patch
  26. 21
      screen/selftest.js
  27. 2
      screen/send/details.js
  28. 60
      screen/send/scanQrAddress.js
  29. 13
      screen/settings/language.js
  30. 2
      screen/transactions/RBF-create.js
  31. 20
      screen/wallets/scanQrWif.js
  32. 2
      tests/integration/Electrum.test.js
  33. 17
      tests/integration/HDWallet.test.js
  34. 2
      tests/integration/hd-segwit-bech32-transaction.test.js
  35. 6
      tests/unit/signer.js

26
BlueComponents.js

@ -1,5 +1,4 @@
/* eslint react/prop-types: 0 */
/* global alert */
/** @type {AppStorage} */
import React, { Component } from 'react';
import Ionicons from 'react-native-vector-icons/Ionicons';
@ -29,13 +28,11 @@ import { LightningCustodianWallet } from './class';
import Carousel from 'react-native-snap-carousel';
import { BitcoinUnit } from './models/bitcoinUnits';
import NavigationService from './NavigationService';
import ImagePicker from 'react-native-image-picker';
import WalletGradient from './class/walletGradient';
import ToolTip from 'react-native-tooltip';
import { BlurView } from '@react-native-community/blur';
import showPopupMenu from 'react-native-popup-menu-android';
import NetworkTransactionFees, { NetworkTransactionFeeType } from './models/networkTransactionFees';
const LocalQRCode = require('@remobile/react-native-qrcode-local-image');
let loc = require('./loc/');
/** @type {AppStorage} */
let BlueApp = require('./BlueApp');
@ -1986,29 +1983,8 @@ export class BlueAddressInput extends Component {
<TouchableOpacity
disabled={this.props.isLoading}
onPress={() => {
Keyboard.dismiss();
ImagePicker.showImagePicker(
{
title: null,
mediaType: 'photo',
takePhotoButtonTitle: null,
customButtons: [{ name: 'navigatetoQRScan', title: 'Use Camera' }],
},
response => {
if (response.customButton) {
NavigationService.navigate('ScanQrAddress', { onBarScanned: this.props.onBarScanned });
} else if (response.uri) {
const uri = response.uri.toString().replace('file://', '');
LocalQRCode.decode(uri, (error, result) => {
if (!error) {
this.props.onBarScanned(result);
} else {
alert('The selected image does not contain a QR Code.');
}
});
}
},
);
Keyboard.dismiss();
}}
style={{
height: 36,

2
BlueElectrum.js

@ -1,7 +1,7 @@
import AsyncStorage from '@react-native-community/async-storage';
import { AppStorage } from './class';
const bitcoin = require('bitcoinjs-lib');
const ElectrumClient = require('electrum-client');
let bitcoin = require('bitcoinjs-lib');
let reverse = require('buffer-reverse');
let BigNumber = require('bignumber.js');

4
MainBottomTabs.js

@ -36,7 +36,7 @@ import receiveDetails from './screen/receive/details';
import setReceiveAmount from './screen/receive/receiveAmount';
import sendDetails from './screen/send/details';
import sendScanQrAddress from './screen/send/scanQrAddress';
import ScanQRCode from './screen/send/scanQrAddress';
import sendCreate from './screen/send/create';
import Confirm from './screen/send/confirm';
import Success from './screen/send/success';
@ -288,7 +288,7 @@ const MainBottomTabs = createStackNavigator(
},
},
ScanQrAddress: {
screen: sendScanQrAddress,
screen: ScanQRCode,
},
LappBrowser: {
screen: LappBrowser,

5
class/abstract-hd-wallet.js

@ -1,7 +1,7 @@
import { LegacyWallet } from './legacy-wallet';
import Frisbee from 'frisbee';
const bip39 = require('bip39');
const bitcoin = require('bitcoinjs-lib');
const bip39 = require('bip39');
const BlueElectrum = require('../BlueElectrum');
export class AbstractHDWallet extends LegacyWallet {
@ -498,8 +498,7 @@ export class AbstractHDWallet extends LegacyWallet {
unspent.vout = unspent.tx_output_n;
unspent.amount = unspent.value;
let chunksIn = bitcoin.script.decompile(Buffer.from(unspent.script, 'hex'));
unspent.address = bitcoin.address.fromOutputScript(chunksIn);
unspent.address = bitcoin.address.fromOutputScript(Buffer.from(unspent.script, 'hex'));
utxos.push(unspent);
}
} catch (err) {

23
class/hd-legacy-breadwallet-wallet.js

@ -1,7 +1,8 @@
import { AbstractHDWallet } from './abstract-hd-wallet';
import Frisbee from 'frisbee';
import bitcoin from 'bitcoinjs-lib';
import bip39 from 'bip39';
const bip32 = require('bip32');
const bitcoinjs = require('bitcoinjs-lib');
/**
* HD Wallet (BIP39).
@ -22,7 +23,7 @@ export class HDLegacyBreadwalletWallet extends AbstractHDWallet {
}
const mnemonic = this.secret;
const seed = bip39.mnemonicToSeed(mnemonic);
const root = bitcoin.HDNode.fromSeedBuffer(seed);
const root = bip32.fromSeed(seed);
const path = "m/0'";
const child = root.derivePath(path).neutered();
@ -36,12 +37,16 @@ export class HDLegacyBreadwalletWallet extends AbstractHDWallet {
if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit
const mnemonic = this.secret;
const seed = bip39.mnemonicToSeed(mnemonic);
const root = bitcoin.HDNode.fromSeedBuffer(seed);
const root = bip32.fromSeed(seed);
const path = "m/0'/0/" + index;
const child = root.derivePath(path);
return (this.external_addresses_cache[index] = child.getAddress());
const address = bitcoinjs.payments.p2pkh({
pubkey: child.publicKey,
}).address;
return (this.external_addresses_cache[index] = address);
}
_getInternalAddressByIndex(index) {
@ -49,12 +54,16 @@ export class HDLegacyBreadwalletWallet extends AbstractHDWallet {
if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit
const mnemonic = this.secret;
const seed = bip39.mnemonicToSeed(mnemonic);
const root = bitcoin.HDNode.fromSeedBuffer(seed);
const root = bip32.fromSeed(seed);
const path = "m/0'/1/" + index;
const child = root.derivePath(path);
return (this.internal_addresses_cache[index] = child.getAddress());
const address = bitcoinjs.payments.p2pkh({
pubkey: child.publicKey,
}).address;
return (this.internal_addresses_cache[index] = address);
}
_getExternalWIFByIndex(index) {
@ -75,7 +84,7 @@ export class HDLegacyBreadwalletWallet extends AbstractHDWallet {
_getWIFByIndex(internal, index) {
const mnemonic = this.secret;
const seed = bip39.mnemonicToSeed(mnemonic);
const root = bitcoin.HDNode.fromSeedBuffer(seed);
const root = bitcoinjs.bip32.fromSeed(seed);
const path = `m/0'/${internal ? 1 : 0}/${index}`;
const child = root.derivePath(path);

28
class/hd-legacy-p2pkh-wallet.js

@ -1,8 +1,9 @@
import { AbstractHDWallet } from './abstract-hd-wallet';
import bitcoin from 'bitcoinjs-lib';
import bip39 from 'bip39';
import BigNumber from 'bignumber.js';
import signer from '../models/signer';
const bitcoin = require('bitcoinjs-lib');
const HDNode = require('bip32');
/**
* HD Wallet (BIP39).
@ -23,7 +24,7 @@ export class HDLegacyP2PKHWallet extends AbstractHDWallet {
}
const mnemonic = this.secret;
const seed = bip39.mnemonicToSeed(mnemonic);
const root = bitcoin.HDNode.fromSeedBuffer(seed);
const root = bitcoin.bip32.fromSeed(seed);
const path = "m/44'/0'/0'";
const child = root.derivePath(path).neutered();
@ -50,22 +51,22 @@ export class HDLegacyP2PKHWallet extends AbstractHDWallet {
_getWIFByIndex(internal, index) {
const mnemonic = this.secret;
const seed = bip39.mnemonicToSeed(mnemonic);
const root = bitcoin.HDNode.fromSeedBuffer(seed);
const root = HDNode.fromSeed(seed);
const path = `m/44'/0'/0'/${internal ? 1 : 0}/${index}`;
const child = root.derivePath(path);
return child.keyPair.toWIF();
return child.toWIF();
}
_getExternalAddressByIndex(index) {
index = index * 1; // cast to int
if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit
const node = bitcoin.HDNode.fromBase58(this.getXpub());
const address = node
.derive(0)
.derive(index)
.getAddress();
const node = bitcoin.bip32.fromBase58(this.getXpub());
const address = bitcoin.payments.p2pkh({
pubkey: node.derive(0).derive(index).publicKey,
}).address;
return (this.external_addresses_cache[index] = address);
}
@ -74,11 +75,10 @@ export class HDLegacyP2PKHWallet extends AbstractHDWallet {
index = index * 1; // cast to int
if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit
const node = bitcoin.HDNode.fromBase58(this.getXpub());
const address = node
.derive(1)
.derive(index)
.getAddress();
const node = bitcoin.bip32.fromBase58(this.getXpub());
const address = bitcoin.payments.p2pkh({
pubkey: node.derive(1).derive(index).publicKey,
}).address;
return (this.internal_addresses_cache[index] = address);
}

2
class/hd-segwit-bech32-transaction.js

@ -1,5 +1,5 @@
import { HDSegwitBech32Wallet, SegwitBech32Wallet } from './';
const bitcoin = require('bitcoinjs5');
const bitcoin = require('bitcoinjs-lib');
const BlueElectrum = require('../BlueElectrum');
const reverse = require('buffer-reverse');
const BigNumber = require('bignumber.js');

29
class/hd-segwit-bech32-wallet.js

@ -3,8 +3,8 @@ import { NativeModules } from 'react-native';
import bip39 from 'bip39';
import BigNumber from 'bignumber.js';
import b58 from 'bs58check';
const bitcoin = require('bitcoinjs-lib');
const BlueElectrum = require('../BlueElectrum');
const bitcoin5 = require('bitcoinjs5');
const HDNode = require('bip32');
const coinSelectAccumulative = require('coinselect/accumulative');
const coinSelectSplit = require('coinselect/split');
@ -693,20 +693,28 @@ export class HDSegwitBech32Wallet extends AbstractHDWallet {
throw new Error('Not enough balance. Try sending smaller amount');
}
let txb = new bitcoin5.TransactionBuilder();
let psbt = new bitcoin.Psbt();
let c = 0;
let keypairs = {};
let values = {};
inputs.forEach(input => {
const keyPair = bitcoin5.ECPair.fromWIF(this._getWifForAddress(input.address));
const keyPair = bitcoin.ECPair.fromWIF(this._getWifForAddress(input.address));
keypairs[c] = keyPair;
values[c] = input.value;
c++;
if (!input.address || !this._getWifForAddress(input.address)) throw new Error('Internal error: no address or WIF to sign input');
const p2wpkh = bitcoin5.payments.p2wpkh({ pubkey: keyPair.publicKey });
txb.addInput(input.txId, input.vout, sequence, p2wpkh.output); // NOTE: provide the prevOutScript!
const p2wpkh = bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey });
psbt.addInput({
hash: input.txId,
index: input.vout,
sequence,
witnessUtxo: {
script: p2wpkh.output,
value: input.value,
},
});
});
outputs.forEach(output => {
@ -715,14 +723,17 @@ export class HDSegwitBech32Wallet extends AbstractHDWallet {
output.address = changeAddress;
}
txb.addOutput(output.address, output.value);
psbt.addOutput({
address: output.address,
value: output.value,
});
});
for (let cc = 0; cc < c; cc++) {
txb.sign(cc, keypairs[cc], null, null, values[cc]); // NOTE: no redeem script
psbt.signInput(cc, keypairs[cc]);
}
const tx = txb.build();
const tx = psbt.finalizeAllInputs().extractTransaction();
return { tx, inputs, outputs, fee };
}
@ -733,7 +744,7 @@ export class HDSegwitBech32Wallet extends AbstractHDWallet {
* @returns {String}
*/
static _nodeToBech32SegwitAddress(hdNode) {
return bitcoin5.payments.p2wpkh({
return bitcoin.payments.p2wpkh({
pubkey: hdNode.publicKey,
}).address;
}

10
class/hd-segwit-p2sh-wallet.js

@ -7,7 +7,6 @@ import b58 from 'bs58check';
import signer from '../models/signer';
import { BitcoinUnit } from '../models/bitcoinUnits';
const bitcoin = require('bitcoinjs-lib');
const bitcoin5 = require('bitcoinjs5');
const HDNode = require('bip32');
const { RNRandomBytes } = NativeModules;
@ -19,6 +18,7 @@ const { RNRandomBytes } = NativeModules;
*/
function ypubToXpub(ypub) {
let data = b58.decode(ypub);
if (data.readUInt32BE() !== 0x049d7cb2) throw new Error('Not a valid ypub extended key!');
data = data.slice(4);
data = Buffer.concat([Buffer.from('0488b21e', 'hex'), data]);
@ -31,8 +31,8 @@ function ypubToXpub(ypub) {
* @returns {String}
*/
function nodeToP2shSegwitAddress(hdNode) {
const { address } = bitcoin5.payments.p2sh({
redeem: bitcoin5.payments.p2wpkh({ pubkey: hdNode.publicKey }),
const { address } = bitcoin.payments.p2sh({
redeem: bitcoin.payments.p2wpkh({ pubkey: hdNode.publicKey }),
});
return address;
}
@ -95,11 +95,11 @@ export class HDSegwitP2SHWallet extends AbstractHDWallet {
_getWIFByIndex(internal, index) {
const mnemonic = this.secret;
const seed = bip39.mnemonicToSeed(mnemonic);
const root = bitcoin.HDNode.fromSeedBuffer(seed);
const root = bitcoin.bip32.fromSeed(seed);
const path = `m/49'/0'/0'/${internal ? 1 : 0}/${index}`;
const child = root.derivePath(path);
return child.keyPair.toWIF();
return bitcoin.ECPair.fromPrivateKey(child.privateKey).toWIF();
}
_getExternalAddressByIndex(index) {

6
class/legacy-wallet.js

@ -3,9 +3,9 @@ import { SegwitBech32Wallet } from './';
import { useBlockcypherTokens } from './constants';
import Frisbee from 'frisbee';
import { NativeModules } from 'react-native';
const bitcoin = require('bitcoinjs-lib');
const { RNRandomBytes } = NativeModules;
const BigNumber = require('bignumber.js');
const bitcoin = require('bitcoinjs-lib');
const signer = require('../models/signer');
const BlueElectrum = require('../BlueElectrum');
@ -85,7 +85,9 @@ export class LegacyWallet extends AbstractWallet {
let address;
try {
let keyPair = bitcoin.ECPair.fromWIF(this.secret);
address = keyPair.getAddress();
address = bitcoin.payments.p2pkh({
pubkey: keyPair.publicKey,
}).address;
} catch (err) {
return false;
}

18
class/segwit-bech-wallet.js

@ -10,9 +10,9 @@ export class SegwitBech32Wallet extends LegacyWallet {
let address;
try {
let keyPair = bitcoin.ECPair.fromWIF(this.secret);
let pubKey = keyPair.getPublicKeyBuffer();
let scriptPubKey = bitcoin.script.witnessPubKeyHash.output.encode(bitcoin.crypto.hash160(pubKey));
address = bitcoin.address.fromOutputScript(scriptPubKey);
address = bitcoin.payments.p2wpkh({
pubkey: keyPair.publicKey,
}).address;
} catch (err) {
return false;
}
@ -23,13 +23,17 @@ export class SegwitBech32Wallet extends LegacyWallet {
static witnessToAddress(witness) {
const pubKey = Buffer.from(witness, 'hex');
const pubKeyHash = bitcoin.crypto.hash160(pubKey);
const scriptPubKey = bitcoin.script.witnessPubKeyHash.output.encode(pubKeyHash);
return bitcoin.address.fromOutputScript(scriptPubKey, bitcoin.networks.bitcoin);
return bitcoin.payments.p2wpkh({
pubkey: pubKey,
network: bitcoin.networks.bitcoin,
}).address;
}
static scriptPubKeyToAddress(scriptPubKey) {
const scriptPubKey2 = Buffer.from(scriptPubKey, 'hex');
return bitcoin.address.fromOutputScript(scriptPubKey2, bitcoin.networks.bitcoin);
return bitcoin.payments.p2wpkh({
output: scriptPubKey2,
network: bitcoin.networks.bitcoin,
}).address;
}
}

27
class/segwit-p2sh-wallet.js

@ -3,6 +3,21 @@ const bitcoin = require('bitcoinjs-lib');
const signer = require('../models/signer');
const BigNumber = require('bignumber.js');
/**
* Creates Segwit P2SH Bitcoin address
* @param pubkey
* @param network
* @returns {String}
*/
function pubkeyToP2shSegwitAddress(pubkey, network) {
network = network || bitcoin.networks.bitcoin;
const { address } = bitcoin.payments.p2sh({
redeem: bitcoin.payments.p2wpkh({ pubkey, network }),
network,
});
return address;
}
export class SegwitP2SHWallet extends LegacyWallet {
static type = 'segwitP2SH';
static typeReadable = 'SegWit (P2SH)';
@ -13,11 +28,7 @@ export class SegwitP2SHWallet extends LegacyWallet {
static witnessToAddress(witness) {
const pubKey = Buffer.from(witness, 'hex');
const pubKeyHash = bitcoin.crypto.hash160(pubKey);
const redeemScript = bitcoin.script.witnessPubKeyHash.output.encode(pubKeyHash);
const redeemScriptHash = bitcoin.crypto.hash160(redeemScript);
const scriptPubkey = bitcoin.script.scriptHash.output.encode(redeemScriptHash);
return bitcoin.address.fromOutputScript(scriptPubkey, bitcoin.networks.bitcoin);
return pubkeyToP2shSegwitAddress(pubKey);
}
getAddress() {
@ -25,14 +36,12 @@ export class SegwitP2SHWallet extends LegacyWallet {
let address;
try {
let keyPair = bitcoin.ECPair.fromWIF(this.secret);
let pubKey = keyPair.getPublicKeyBuffer();
let pubKey = keyPair.publicKey;
if (!keyPair.compressed) {
console.warn('only compressed public keys are good for segwit');
return false;
}
let witnessScript = bitcoin.script.witnessPubKeyHash.output.encode(bitcoin.crypto.hash160(pubKey));
let scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(witnessScript));
address = bitcoin.address.fromOutputScript(scriptPubKey);
address = pubkeyToP2shSegwitAddress(pubKey);
} catch (err) {
return false;
}

4
ios/BlueWallet.xcodeproj/project.pbxproj

@ -1155,7 +1155,7 @@
PROVISIONING_PROFILE_SPECIFIER = "io.bluewallet.bluewallet AppStore";
SWIFT_OBJC_BRIDGING_HEADER = "BlueWallet-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
@ -1188,7 +1188,7 @@
PRODUCT_NAME = BlueWallet;
PROVISIONING_PROFILE_SPECIFIER = "io.bluewallet.bluewallet AppStore";
SWIFT_OBJC_BRIDGING_HEADER = "BlueWallet-Bridging-Header.h";
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};

20
ios/BlueWallet/Base.lproj/LaunchScreen.xib

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -14,26 +14,10 @@
<view contentMode="scaleToFill" id="iN0-l3-epB">
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="icon" translatesAutoresizingMaskIntoConstraints="NO" id="5GZ-ze-kHb">
<rect key="frame" x="154" y="184" width="173" height="112"/>
<constraints>
<constraint firstAttribute="height" constant="112" id="Zm5-a8-A8d"/>
<constraint firstAttribute="width" constant="173" id="t2s-74-qaQ"/>
</constraints>
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="5GZ-ze-kHb" firstAttribute="centerX" secondItem="iN0-l3-epB" secondAttribute="centerX" id="UkL-ek-FgS"/>
<constraint firstItem="5GZ-ze-kHb" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="centerY" id="mWu-9a-P4V"/>
</constraints>
<nil key="simulatedStatusBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="547.20000000000005" y="454.27286356821594"/>
</view>
</objects>
<resources>
<image name="icon" width="512" height="512"/>
</resources>
</document>

16
ios/Podfile.lock

@ -86,17 +86,17 @@ PODS:
- React-jsinspector (0.60.5)
- react-native-blur (0.8.0):
- React
- react-native-camera (2.11.1):
- react-native-camera (3.4.0):
- React
- react-native-camera/RCT (= 2.11.1)
- react-native-camera/RN (= 2.11.1)
- react-native-camera/RCT (2.11.1):
- react-native-camera/RCT (= 3.4.0)
- react-native-camera/RN (= 3.4.0)
- react-native-camera/RCT (3.4.0):
- React
- react-native-camera/RN (2.11.1):
- react-native-camera/RN (3.4.0):
- React
- react-native-haptic-feedback (1.7.1):
- React
- react-native-image-picker (0.28.1):
- react-native-image-picker (1.1.0):
- React
- react-native-randombytes (3.5.3):
- React
@ -339,9 +339,9 @@ SPEC CHECKSUMS:
React-jsiexecutor: 90ad2f9db09513fc763bc757fdc3c4ff8bde2a30
React-jsinspector: e08662d1bf5b129a3d556eb9ea343a3f40353ae4
react-native-blur: cad4d93b364f91e7b7931b3fa935455487e5c33c
react-native-camera: 96a3c81f27da57b816fbb6808dde20dc96d1431e
react-native-camera: 203091b4bf99d48b788a0682ad573e8718724893
react-native-haptic-feedback: 22c9dc85fd8059f83bf9edd9212ac4bd4ae6074d
react-native-image-picker: fd93361c666f397bdf72f9c6c23f13d2685b9173
react-native-image-picker: 3637d63fef7e32a230141ab4660d3ceb773c824f
react-native-randombytes: 991545e6eaaf700b4ee384c291ef3d572e0b2ca8
react-native-slider: 6d83f7b8076a84e965a43fbdcfcf9dac19cea42e
react-native-webview: f72ac4078e115dfa741cc588acb1cca25566457d

236
loc/ZAR_Afr.js

@ -0,0 +1,236 @@
module.exports = {
_: {
storage_is_encrypted: 'U geheue spasie is nou ge-enkripteer. ‘n Wagwoord word benodig om toegang te verkry. ',
enter_password: 'Sleutel wagwoord in',
bad_password: 'Verkeerde wagwoord, probeer weer',
never: 'nooit',
continue: 'Gaan voort',
ok: 'OK',
},
wallets: {
select_wallet: 'Kies Beursie',
options: 'opsies',
createBitcoinWallet:
'U het nie huidiglik `n geldige Bitcoin Beursie nie. Skep of voer eers ‘n Bitcoin Beursie in, sodat ‘n Bitcoin Lightning Beursie geskep en bevonds mag word. Wil U voortgaan?',
list: {
app_name: 'BlueWallet',
title: 'beursies',
header:
'U beursie verteenwoordig ‘n sleutelkombinasie, bestaande uit geheims (privaat sleutel) en address' +
'wat u kan gebruik om fondse te ontvang.',
add: 'Skep Beursie',
create_a_wallet: 'Skep ‘n beursie',
create_a_wallet1: 'Dit is gratis so skep',
create_a_wallet2: 'soveel as wat u benodig',
latest_transaction: 'laaste transaksie',
empty_txs1: 'U transaksies is hier beskikbaar,',
empty_txs2: 'huidiglik geen transaksies',
tap_here_to_buy: 'Raak hier om Bitcoin te koop',
},
reorder: {
title: 'Herorganiseer Beursies',
},
add: {
title: 'skep beursie',
description:
'U kan ‘n beursie invoer (in WIF - Wallet Import Format), of ‘n nuwe beursie skep. Beursies ondersteun Segwit as standaard.',
scan: 'Skandeer',
create: 'Skep',
label_new_segwit: 'Nuwe SegWit',
label_new_lightning: 'Nuwe Lightning',
wallet_name: 'beursie naam',
wallet_type: 'tipe',
or: 'of',
import_wallet: 'Beursie Invoer',
imported: 'Ingevoer',
coming_soon: 'In die toekoms',
lightning: 'Lightning',
bitcoin: 'Bitcoin',
},
details: {
title: 'Beursiet',
address: 'AdresAddress',
type: 'Tipe',
label: 'Etiket',
destination: 'bestemming',
description: 'beskrywing',
are_you_sure: 'Is u Seker?',
yes_delete: 'Ja, vernietig',
no_cancel: 'Nee, kanseleerl',
delete: 'Vernietig',
save: 'Berg',
delete_this_wallet: 'Vernietig hierdie beursie',
export_backup: 'voer uit / kopieer',
buy_bitcoin: 'Koop Bitcoin',
show_xpub: 'Wys beursie XPUB',
},
export: {
title: 'beursie uitvoer',
},
xpub: {
title: 'beursie XPUB',
copiedToClipboard: 'Gestuur na klipbord.',
},
import: {
title: 'Invoer',
explanation:
'Sleutel mnemonic, privaat sleutel, WIF, of enige text verwysing. BlueWallet sal die korrekte formaat kies en u beursie importeer ',
imported: 'Invoer suksesvol',
error: 'U invoer het misluk. Maak asseblief seker u data is korrek en geldig.',
success: 'Suksesvol',
do_import: 'Invoer',
scan_qr: 'of skandeer QR kode?',
},
scanQrWif: {
go_back: 'Gaan Terug',
cancel: 'Kanseleer',
decoding: 'Decoding',
input_password: 'Input password',
password_explain: 'Hierdie is ‘n BIP38 ge-enkripteerde privaat sleutel',
bad_password: 'Wagwoord verkeerd',
wallet_already_exists: 'Hierdie beursie bestaan alreeds',
bad_wif: 'WIF verkeerd',
imported_wif: 'WIF invoer suksesvol ',
with_address: ' met adres ',
imported_segwit: 'Segwit Invoer Suksesvol',
imported_legacy: 'Legacy Invoer',
imported_watchonly: 'Kyk Slegs invoer suksesvol',
},
},
transactions: {
list: {
tabBarLabel: 'Transaksies',
title: 'transaksies',
description: 'Lys met inkomende en uitgaande transaksies van u beursies',
conf: 'bev.',
},
details: {
title: 'Transaksie',
from: 'Inset',
to: 'Resultaat',
copy: 'Kopieer',
transaction_details: 'Transaksie besonderhede',
show_in_block_explorer: 'Wys in blok verkenner',
},
},
send: {
header: 'Stuur',
details: {
title: 'skep transaksie',
amount_field_is_not_valid: 'Bedrag is ongeldig',
fee_field_is_not_valid: 'Fooi spasie is ongeldig',
address_field_is_not_valid: 'Adres is ongeldig',
total_exceeds_balance: 'Die bedrag is meer as die beskikbare balans.',
create_tx_error: 'Daar was ‘n probleem met die skepping van die transaksie. Bevestig asseblief die adres is geldig.',
address: 'adres',
amount_placeholder: 'bedrag om te stuur (in BTC)',
fee_placeholder: 'plus transaksie fooi (in BTC)',
note_placeholder: 'persoonlike notas',
cancel: 'Kanselleer',
scan: 'Skandeer',
send: 'Stuur',
create: 'Skep',
remaining_balance: 'Oorblywende balans',
},
confirm: {
header: 'Bevestig',
sendNow: 'Stuur nou',
},
success: {
done: 'Klaar',
},
create: {
details: 'Besonderhede',
title: 'skep transaksie',
error: 'Daar is ‘n probleem met die transaksie. Ongeldige adres of bedrag?',
go_back: 'Gaan Terug',
this_is_hex: 'Hierdie is die transaksie hex, geteken en gereed om na die netwerk uitgesaai te word.',
to: 'Aan',
amount: 'Bedrag',
fee: 'Fooi',
tx_size: 'TX groote',
satoshi_per_byte: 'Satoshi per byte',
memo: 'Memo',
broadcast: 'Saai uit',
not_enough_fee: 'Fooi te laag. Vermeerder die fooi',
},
},
receive: {
header: 'Ontvang',
details: {
title: 'Deel adres met krediteur',
share: 'deel',
copiedToClipboard: 'Gekopieer na klipbord.',
label: 'Beskrywing',
create: 'Skep',
setAmount: 'Bedrag ontvang',
},
},
buyBitcoin: {
header: 'Koop Bitcoin',
tap_your_address: 'Raak aan die adres om dit na die klipbord the stuur:',
copied: 'Gekopieer na klipbord!',
},
settings: {
header: 'instellings',
plausible_deniability: 'Geloofwaardige ontkenbaarheid...',
storage_not_encrypted: 'Berging: Nie-geenkripteer nie',
storage_encrypted: 'Berging: Ge-enkripteer',
password: 'Wagwoord',
password_explain: 'Skep die wagwoord wat u sal gebruik om u berging te de-enkripteer',
retype_password: 'Hervoer wagwoord',
passwords_do_not_match: 'Wagwoorde stem nie oor een nie',
encrypt_storage: 'Enkripteer Berging',
lightning_settings: 'Lightning Instellings',
lightning_settings_explain:
'Om u eie LND node te konnekteer, installeer asseblief LndHub' +
' and put its URL here in settings. Leave blank om die standaard LndHub' +
'(lndhub.io) te gebruik',
save: 'stoor',
about: 'info',
language: 'Taal',
currency: 'Geldeenheid',
},
plausibledeniability: {
title: 'Geloofwaardige Ontkenbaarheid',
help:
'Onder sekere omstandighede mag u dalk geforseer word om u ' +
'wagwoord te deel teen u wil. Om u te beskerm kan Bluewallet ‘n ' +
'tweede “fantasie” beursie skep wat as skerm kan dien. Indien u ' +
'hierdie wagwoord deel sal die 3de party nie toegang tot u hoof fondse kry nie.',
help2: 'Fantasie berging is heeltemal funksioneel',
create_fake_storage: 'Skep fantasie berging wagwoord',
go_back: 'Gaan terug',
create_password: 'Skep ‘n wagwoord',
create_password_explanation: 'Die wagwoord vir fantasie berging moet verskil van die wagwoord vir hoof berging',
password_should_not_match: 'Die wagwoord vir fantasie berging moet verskil van die wagwoord vir hoof berging.',
retype_password: 'Hervoer wagwoord',
passwords_do_not_match: 'Wagwoorde vergelyk nie, probeer weer',
success: 'Sukses',
},
lnd: {
title: 'bestuur fondse',
choose_source_wallet: 'Kies ‘n bron beursie',
refill_lnd_balance: 'Herlaai Lightning beursie',
refill: 'Herlaai',
withdraw: 'Ontrek',
expired: 'Verval',
placeholder: 'Faktuur',
sameWalletAsInvoiceError: 'U kan nie ‘n faktuur betaal met die selfde beursie waarmee die faktuur geksep is nie.',
},
pleasebackup: {
title: 'Your wallet is created...',
text:
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device. You can use Electrum wallet on desktop (https://electrum.org/) to restore the same wallet.",
ok: 'OK, I wrote this down!',
},
lndViewInvoice: {
wasnt_paid_and_expired: 'This invoice was not paid for and has expired',
has_been_paid: 'This invoice has been paid for',
please_pay: 'Please pay',
sats: 'sats',
for: 'For:',
additional_info: 'Additional Information',
open_direct_channel: 'Open direct channel with this node:',
},
};

239
loc/ZAR_Xho.js

@ -0,0 +1,239 @@
module.exports = {
_: {
storage_is_encrypted: 'Ukugcinwa kwakho kubhaliwe. Inombolo yokuvula iyadingeka ukuba ichithwe',
enter_password: 'Faka inombolo yokuvula',
bad_password: 'Iphasiwedi engalunganga, zama kwakhona',
never: 'Ungalingi',
continue: 'Qhubeka',
ok: 'OK',
},
wallets: {
select_wallet: 'Khetha ingxowa',
options: 'Ukhetho',
createBitcoinWallet:
'Okwangoku awunayo ingxowa yebitcoin. Ukuze kuxhaswe ingxowa ekhawulezayo, Ingxowa yeBitcoin kufuneka idalwe okanye ikhutshelwe. Ungathanda ukuqhubeka ?',
list: {
app_name: 'BlueWallet',
title: 'Ingxowa',
header: 'Ingxowa imele ukuba nemfihlelo yokuyivula nekheli kwaye unokuyisebenzisa ukwamkela imali.',
add: 'Yongeza Ingxowa',
create_a_wallet: 'Yenza ingxowa',
create_a_wallet1: 'Ayihlawulelwa kwaye ungayenza',
create_a_wallet2: 'Ungenza zibeninzi indlela zokuhlawula',
latest_transaction: 'Utshintsho olutsha',
empty_txs1: 'Intengiso yakho iya kubonakala apha,',
empty_txs2: 'akuho nanye okwangoku',
tap_here_to_buy: 'Cofa apha ukuthenga ibitcoin',
},
reorder: {
title: 'Yenza kwakhona ingxowa',
},
add: {
title: 'yongeza ingxowa',
description:
'Unokukhangela ingxowa yephepha yokugcinwa kwephepha ( kwi-WIF – indlela lokungenisa ingxowa), okanye wenze ingxowa entsha. Ingxowa yeSegwit exhaswa yi-default.',
scan: 'Ukuqondisa',
create: 'Yakha',
label_new_segwit: 'SegWit entsha',
label_new_lightning: 'Umbane omtsha',
wallet_name: 'igama lengxowa',
wallet_type: 'uhlobo',
or: 'okanye',
import_wallet: 'Ukungenisa ingxowa',
imported: 'ngeniswa',
coming_soon: 'Kuza ngokukhawuleza',
lightning: 'Umbane',
bitcoin: 'Bitcoin',
},
details: {
title: 'Ingxowa',
address: 'Ikheli',
type: 'Uhlobo',
label: 'Igama',
destination: 'ukuya kuyo',
description: 'ukuya kuyo',
are_you_sure: 'Ingaba uqinisekile?',
yes_delete: 'Ewe, yisuse',
no_cancel: 'Hayi, rhoxisa',
delete: 'Cima',
save: 'Gcina',
delete_this_wallet: 'Cima le ngxowa',
export_backup: 'Ukuthumela ngaphandle / yokugcina',
buy_bitcoin: 'Thenga ibitcoin',
show_xpub: 'Bonise ingxowa XPUB',
},
export: {
title: 'ukuthunyelwa kweebhanki ',
},
xpub: {
title: 'ingxowa XPUB',
copiedToClipboard: 'Ikopishwe kwi-clipboard',
},
import: {
title: 'ukungenisa',
explanation:
'Bhale apha imnemonic yakho, ngundoqo, WIF , okanye nantoni na onayo. BlueWallet uya kwenza konke okusemandleni ukuqiqa ifomathi efanelekileyo kwaye ingenise ingxowa yakho',
imported: 'Ngenisiwe',
error: 'Ayiphumelelanga ukungenisa. Nceda, uqiniseka ukuba idata ehlinzekiweyo iyasebenza.',
success: 'Iphumelele',
do_import: 'Ngeniswe',
scan_qr: 'okanye ukukhangela iQR code?',
},
scanQrWif: {
go_back: 'Buya Umva',
cancel: 'Rhoxisa',
decoding: 'Ukumisela',
input_password: 'Igama lokungena',
password_explain: 'Yi le BIP38 ikhifidi yangasese itsixe',
bad_password: 'Inombolo yokuvula eli ngalunganga ',
wallet_already_exists: 'Ikhredithi enjalo sele ikhona',
bad_wif: 'Ezimbi WIF',
imported_wif: 'Ngeniswa WIF ',
with_address: ' Nge dilesi ',
imported_segwit: 'Ngeniswa SegWit',
imported_legacy: 'Ngeniswa ilifa',
imported_watchonly: 'Ngeniswa bukele-kuphela',
},
},
transactions: {
list: {
tabBarLabel: 'Ngeniswa',
title: 'ngeniswa',
description: 'Uluhlu lokungena okanye ukuphuma kweekhredithi zakho',
conf: 'conf',
},
details: {
title: 'Ngeniswa',
from: 'Negalelo',
to: 'Mveliso',
copy: 'Ikopi',
transaction_details: 'Iinkcukacha zentengiselwano',
show_in_block_explorer: 'Bonisa ibhloko umhloi',
},
},
send: {
header: 'Thumela',
details: {
title: 'ukudala ukuthenga',
amount_field_is_not_valid: 'intsimi yexabiso ayivumelekanga',
fee_field_is_not_valid: 'Intsimi yentlawulo ayivumelekanga ',
address_field_is_not_valid: 'Intsimi yeedilesi ayivumelekanga',
total_exceeds_balance: 'Imali yokuthumela idlula imali ekhoyo.',
create_tx_error: 'Kukho impazamo yokudala ukuthengiselana. Nceda, qinisekisa ukuba idilesi iyasebenza.',
address: 'idilesi',
amount_placeholder: 'inani lokuthumela (nge BTC)',
fee_placeholder: 'kunye nentlawulo yokuthengiswa (nge BTC)',
note_placeholder: 'inqaku kumntu',
cancel: 'Rhoxisa',
scan: 'Ukutshekisha',
send: 'Thumela',
create: 'Yenza',
remaining_balance: 'Ibhalansi eseleyo',
},
confirm: {
header: 'Qiniseka',
sendNow: 'Thumela ngoku',
},
success: {
done: 'Kwenzekile',
},
create: {
details: 'Iinkcukacha',
title: 'ukudala ukuthenga',
error: 'Impazamo yokudala ukuthengiselana. Idilesi engavumelekanga okanye imali yokuthumela?',
go_back: 'Buya umva',
this_is_hex: 'Le yi ntengo hex, ityikityiwe ilungele ukukhutshelwa kumnatha.',
to: 'Iya ku',
amount: 'Isixa',
fee: 'Ntlawulo',
tx_size: 'TX ubungakanani',
satoshi_per_byte: 'Satoshi nge-byte',
memo: 'Memo',
broadcast: 'Sasazwa',
not_enough_fee: 'Akukho mali e neleyo. UKwandisa intlawulo ',
},
},
receive: {
header: 'Fumana',
details: {
title: 'Wabelane ngale dilesi nomhlawuli',
share: 'yabelana',
copiedToClipboard: 'Ikhutshelwe kwi-clipboard',
label: 'Inkcazo',
create: 'Yenza',
setAmount: 'Fumana ngexabiso',
},
},
buyBitcoin: {
header: 'Thenga Ibitcoin',
tap_your_address: 'Thepha idilesi yakho ukuyikopisha kwi-clipboard:',
copied: 'Ikhutshelwe kwi-clipboard!',
},
settings: {
header: 'Izicwangciso',
plausible_deniability: 'Ukuphika...',
storage_not_encrypted: 'Ukugciwa: hayi ngekhodi',
storage_encrypted: 'Ukugciwa: ngekhodi',
password: 'Inombolo yokuvula',
password_explain: 'Ukudala iinombolo yokuvula oyisebenzisayo ukucima ukugcina',
retype_password: 'Phina inombolo yokuvula',
passwords_do_not_match: 'Inombolo yokuvula azifani',
encrypt_storage: 'Kubhala u kubhala',
lightning_settings: 'Izixhobo zokukhanyisa',
lightning_settings_explain:
'Ukuxhuma kwi-node yakho ye-LND nceda ufake iLndHub' +
' kwaye ufake iURL apha izicwangciso. Shiya kungenanto yokusebenzisa iLndHub (Indhub.io)',
save: 'ndoloza',
about: 'Malunga',
language: 'Ulwimi',
currency: 'Lwemali',
},
plausibledeniability: {
title: 'Ukuphika',
help:
'Phantsi kweemeko unokunyaneliswa ukuba uchaze a ' +
'inombolo yokuvula. BlueWallet ukugcina imali yakho ikhuselekile, unokudala enye ' +
'ukugcinwa kwekhowudi, ngegama eligqithisiweyo. Phantsi kwefuthe, ' +
'unako ukutyhila le phasiwedi kumntu wesithatu. Ukuba ungenayo ' +
'BlueWallet, iya kuvula ukugcinwa kwetyala ‘entsha’. Oku kuya kubonakala ' +
'Umlenze kumntu wesithathu kodwa uza kugcina ngasese ukugcinwa kwakho ' +
'ngemali ekhuselekile..',
help2:
'Igumbi lokugcina elitsha liza kusebenza ngokupheleleyo, kwaye unako ukugcina okunye ‘ + ‘lxabiso elincinci apho likhangeleka ngakumbi.',
create_fake_storage: 'Ukudala igumbi lokugcina elifihlakeleyo',
go_back: 'Buya Umva',
create_password: 'Yenza inombolo yokuvula',
create_password_explanation:
'Inombolo yakho yokuvula igumbi lokugcina inkohliso akumele ifane ne nombolo yokuvula igumbi lakho elinyanisekileyo',
password_should_not_match:
'Inombolo yakho yokuvula igumbi lokugcina inkohliso akumele ifane ne nombolo yokuvula igumbi lakho elinyanisekileyo',
retype_password: 'Phinda inombolo yokuvula',
passwords_do_not_match: 'Inombolo yokuvula ayihambelani, zama kwakhona',
success: 'Iphumelele',
},
lnd: {
title: 'lawula imali',
choose_source_wallet: 'Ukhethe ingxowa yomthombo',
refill_lnd_balance: 'Gcwalisa ingxowa yakho yemali',
refill: 'Gcwalisa',
withdraw: 'Khupha imali',
expired: 'Iphelewe lixesha',
placeholder: 'Invoyisi',
sameWalletAsInvoiceError: ': Awukwazi ukuhlawula i-invoyisi kunye ngengxowa oyisebenzisile ukudala leyo invoyisi.',
},
pleasebackup: {
title: 'Your wallet is created...',
text:
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device. You can use Electrum wallet on desktop (https://electrum.org/) to restore the same wallet.",
ok: 'OK, I wrote this down!',
},
lndViewInvoice: {
wasnt_paid_and_expired: 'This invoice was not paid for and has expired',
has_been_paid: 'This invoice has been paid for',
please_pay: 'Please pay',
sats: 'sats',
for: 'For:',
additional_info: 'Additional Information',
open_direct_channel: 'Open direct channel with this node:',
},
};

7
loc/index.js

@ -28,6 +28,10 @@ dayjs.extend(relativeTime);
lang = 'zh-cn';
require('dayjs/locale/zh-cn');
break;
case 'zh_tw':
lang = 'zh-tw';
require('dayjs/locale/zh-tw');
break;
case 'ru':
require('dayjs/locale/ru');
break;
@ -117,10 +121,13 @@ strings = new Localization({
hu_hu: require('./hu_HU.js'),
id_id: require('./id_ID.js'),
zh_cn: require('./zh_cn.js'),
zh_tw: require('./zh_tw.js'),
sv_se: require('./sv_SE.js'),
nb_no: require('./nb_NO.js'),
tr_tr: require('./tr_TR.js'),
vi_vn: require('./vi_VN.js'),
zar_xho: require('./ZAR_Xho.js'),
zar_afr: require('./ZAR_Afr.js'),
});
strings.saveLanguage = lang => AsyncStorage.setItem(AppStorage.LANG, lang);

4
loc/th_TH.js

@ -24,8 +24,8 @@ module.exports = {
empty_txs1: 'ธุรกรรมจะปรากฏที่นี่,',
empty_txs2: 'ไม่มี ณ ขณะนี้',
empty_txs1_lightning:
'Lightning wallet should be used for your daily transactions. Fees are unfairly cheap and speed is blazing fast.',
empty_txs2_lightning: '\nTo start using it tap on "manage funds" and topup your balance.',
'ควรใช้ไลท์นิงเน็ตเวิร์คสำหรับธุรกรรมประจำวันเท่านั้น ธุรกรรมทันใจและมีค่าธรรมเนียมน้อยมาก',
empty_txs2_lightning: '\nแตะที่ "จัดการเงิน" เพื่อเริ่มใช้งาน และเติมเงิน',
tap_here_to_buy: 'กดที่นี่เพื่อซื้อบิตคอยน์',
},
reorder: {

235
loc/zh_tw.js

@ -0,0 +1,235 @@
module.exports = {
_: {
storage_is_encrypted: '你的資訊已經被加密, 請輸入密碼解密',
enter_password: '輸入密碼',
bad_password: '密碼無效,請重試',
never: '不',
continue: '繼續',
ok: '好的',
},
wallets: {
select_wallet: '選擇錢包',
options: '選項',
createBitcoinWallet: '您當前沒有bitcoin錢包. 為了支援閃電錢包, 我們需要建立或者匯入一個比特幣錢包. 是否需要繼續?',
list: {
app_name: 'BlueWallet',
title: '錢包',
header: '一個錢包代表一對的私鑰和地址' + '你可以通過分享收款.',
add: '新增錢包',
create_a_wallet: '建立一個錢包',
create_a_wallet1: '建立錢包是免費的,你可以',
create_a_wallet2: '想建立多少就建立多少個',
latest_transaction: '最近的轉賬',
empty_txs1: '你的轉賬資訊將展示在這裡',
empty_txs2: '當前無資訊',
empty_txs1_lightning:
'Lightning wallet should be used for your daily transactions. Fees are unfairly cheap and speed is blazing fast.',
empty_txs2_lightning: '\nTo start using it tap on "manage funds" and topup your balance.',
tap_here_to_buy: '點選購買比特幣',
},
reorder: {
title: '重新排列錢包',
},
add: {
title: '新增錢包',
description: '你可以掃描你的紙質備份錢包 (WIF格式), 或者建立一個新錢包. 預設支援隔離見證錢包',
scan: '掃描',
create: '建立',
label_new_segwit: '新隔離見證(Segwit)',
label_new_lightning: '新閃電',
wallet_name: '錢包名稱',
wallet_type: '類型',
or: '或',
import_wallet: '匯入錢包',
imported: '已經匯入',
coming_soon: '即將來臨',
lightning: '閃電',
bitcoin: '比特幣',
},
details: {
title: '錢包',
address: '地址',
type: '類型',
label: '標籤',
destination: '目的',
description: '描述',
are_you_sure: '你確認麼?',
yes_delete: '是的,刪除',
no_cancel: '不,取消',
delete: '刪除',
save: '儲存',
delete_this_wallet: '刪除這個錢包',
export_backup: '匯出備份',
buy_bitcoin: '購買比特幣',
show_xpub: '展示錢包 XPUB',
},
export: {
title: '錢包匯出',
},
xpub: {
title: '錢包 XPUB',
copiedToClipboard: '複製到貼上板.',
},
import: {
title: '匯入',
explanation: '輸入你的助記詞,私鑰或者WIF, 或者其他格式的資料. BlueWallet將盡可能的自動識別資料格式並匯入錢包',
imported: '已經匯入',
error: '匯入失敗,請確認你提供的資訊是有效的',
success: '成功',
do_import: '匯入',
scan_qr: '或掃面二維碼',
},
scanQrWif: {
go_back: '回退',
cancel: '取消',
decoding: '解碼中',
input_password: '輸入密碼',
password_explain: '這是一個BIP38加密的私鑰',
bad_password: '密碼錯誤',
wallet_already_exists: '當前錢包已經存在',
bad_wif: 'WIF格式無效',
imported_wif: 'WIF已經匯入',
with_address: ' 地址為',
imported_segwit: 'SegWit已經匯入',
imported_legacy: 'Legacy已經匯入',
imported_watchonly: '匯入只讀',
},
},
transactions: {
list: {
tabBarLabel: '轉賬',
title: '轉賬',
description: '當前所有錢包的轉入和轉出記錄',
conf: '配置',
},
details: {
title: '轉賬',
from: '輸入',
to: '輸出',
copy: '複製',
transaction_details: '轉賬詳情',
show_in_block_explorer: '區塊瀏覽器展示',
},
},
send: {
header: '傳送',
details: {
title: '建立交易',
amount_field_is_not_valid: '金額格式無效',
fee_field_is_not_valid: '費用格式無效',
address_field_is_not_valid: '地址內容無效',
total_exceeds_balance: '餘額不足',
create_tx_error: '建立交易失敗. 請確認地址格式正確.',
address: '地址',
amount_placeholder: '傳送金額(in BTC)',
fee_placeholder: '手續費用 (in BTC)',
note_placeholder: '訊息',
cancel: '取消',
scan: '掃描',
send: '傳送',
create: '建立',
remaining_balance: '剩餘金額',
},
confirm: {
header: '確認',
sendNow: '現在傳送',
},
success: {
done: '完成',
},
create: {
details: '詳情',
title: '建立詳情',
error: '建立交易失敗. 無效地址或金額?',
go_back: '回退',
this_is_hex: '這個是交易的十六進位制資料, 簽名並廣播到全網路.',
to: '到',
amount: '金額',
fee: '手續費',
tx_size: '交易大小',
satoshi_per_byte: '蔥每byte',
memo: '訊息',
broadcast: '廣播',
not_enough_fee: '手續費不夠,請增加手續費',
},
},
receive: {
header: '收款',
details: {
title: '分享這個地址給付款人',
share: '分享',
copiedToClipboard: '複製到貼上板.',
label: '描述',
create: '建立',
setAmount: '收款金額',
},
},
buyBitcoin: {
header: '購買比特幣',
tap_your_address: '點選地址複製到貼上板:',
copied: '複製到貼上板!',
},
settings: {
header: '設定',
plausible_deniability: '可否認性...',
storage_not_encrypted: '儲存:未加密',
storage_encrypted: '儲存:加密中',
password: '密碼',
password_explain: '建立你的加密密碼',
retype_password: '再次輸入密碼',
passwords_do_not_match: '兩次輸入密碼不同',
encrypt_storage: '加密儲存',
lightning_settings: '閃電網路設定',
lightning_settings_explain: '如要要連線你自己的閃電節點請安裝LndHub' + ' 並把url地址輸入到下面. 空白將使用預設的LndHub (lndhub.io)',
save: '儲存',
about: '關於',
language: '語言',
currency: '貨幣',
advanced_options: 'Advanced Options',
enable_advanced_mode: 'Enable advanced mode',
},
plausibledeniability: {
title: '可否認性',
help:
'在某些情況下, 你不得不暴露 ' +
'密碼. 為了讓你的比特幣更加安全, BlueWallet可以建立一些 ' +
'加密空間, 用不同的密碼. 在壓力之下, ' +
'你可以暴露這個錢包密碼. 再次進入 ' +
'BlueWallet, 我們會解鎖一些虛擬空間. 對第三方來說看上去' +
'是合理的, 但會偷偷的幫你保證主錢包的安全 ' +
'幣也就安全了.',
help2: '新的空間具備完整的功能,你可以存在 ' + '少量的金額在裡面.',
create_fake_storage: '建立虛擬加密儲存',
go_back: '回退',
create_password: '建立密碼',
create_password_explanation: '虛擬儲存空間密碼不能和主儲存空間密碼相同',
password_should_not_match: '虛擬儲存空間密碼不能和主儲存空間密碼相同',
retype_password: '重輸密碼',
passwords_do_not_match: '兩次輸入密碼不同,請重新輸入',
success: '成功',
},
lnd: {
title: '配置資金支援',
choose_source_wallet: '選擇一個資金源錢包',
refill_lnd_balance: '給閃電錢包充值',
refill: '充值',
withdraw: '提取',
expired: '超時',
sameWalletAsInvoiceError: '你不能用建立賬單的錢包去支付該賬單',
},
pleasebackup: {
title: 'Your wallet is created...',
text:
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device. You can use Electrum wallet on desktop (https://electrum.org/) to restore the same wallet.",
ok: 'OK, I wrote this down!',
},
lndViewInvoice: {
wasnt_paid_and_expired: 'This invoice was not paid for and has expired',
has_been_paid: 'This invoice has been paid for',
please_pay: 'Please pay',
sats: 'sats',
for: 'For:',
additional_info: 'Additional Information',
open_direct_channel: 'Open direct channel with this node:',
},
};

168
models/signer.js

@ -6,13 +6,16 @@
* https://github.com/Overtorment/Cashier-BTC
*
**/
let bitcoinjs = require('bitcoinjs-lib');
const bitcoinjs = require('bitcoinjs-lib');
const _p2wpkh = bitcoinjs.payments.p2wpkh;
const _p2sh = bitcoinjs.payments.p2sh;
const toSatoshi = num => parseInt((num * 100000000).toFixed(0));
exports.createHDTransaction = function(utxos, toAddress, amount, fixedFee, changeAddress) {
let feeInSatoshis = parseInt((fixedFee * 100000000).toFixed(0));
let amountToOutputSatoshi = parseInt(((amount - fixedFee) * 100000000).toFixed(0)); // how much payee should get
let txb = new bitcoinjs.TransactionBuilder();
txb.setVersion(1);
let unspentAmountSatoshi = 0;
let ourOutputs = {};
let outputNum = 0;
@ -50,7 +53,11 @@ exports.createHDTransaction = function(utxos, toAddress, amount, fixedFee, chang
// now, signing every input with a corresponding key
for (let c = 0; c <= outputNum; c++) {
txb.sign(c, ourOutputs[c].keyPair);
txb.sign({
prevOutScriptType: 'p2pkh',
vin: c,
keyPair: ourOutputs[c].keyPair,
});
}
let tx = txb.build();
@ -60,23 +67,35 @@ exports.createHDTransaction = function(utxos, toAddress, amount, fixedFee, chang
exports.createHDSegwitTransaction = function(utxos, toAddress, amount, fixedFee, changeAddress) {
let feeInSatoshis = parseInt((fixedFee * 100000000).toFixed(0));
let amountToOutputSatoshi = parseInt(((amount - fixedFee) * 100000000).toFixed(0)); // how much payee should get
let txb = new bitcoinjs.TransactionBuilder();
let psbt = new bitcoinjs.Psbt();
psbt.setVersion(1);
let unspentAmountSatoshi = 0;
let ourOutputs = {};
let ourOutputs = [];
let outputNum = 0;
for (const unspent of utxos) {
if (unspent.confirmations < 1) {
// using only confirmed outputs
continue;
}
txb.addInput(unspent.txid, unspent.vout);
ourOutputs[outputNum] = ourOutputs[outputNum] || {};
let keyPair = bitcoinjs.ECPair.fromWIF(unspent.wif);
let pubKey = keyPair.getPublicKeyBuffer();
let pubKeyHash = bitcoinjs.crypto.hash160(pubKey);
let redeemScript = bitcoinjs.script.witnessPubKeyHash.output.encode(pubKeyHash);
let p2wpkh = _p2wpkh({
pubkey: keyPair.publicKey,
});
let p2sh = _p2sh({
redeem: p2wpkh,
});
psbt.addInput({
hash: unspent.txid,
index: unspent.vout,
witnessUtxo: {
script: p2sh.output,
value: unspent.amount,
},
redeemScript: p2wpkh.output,
});
ourOutputs[outputNum] = ourOutputs[outputNum] || {};
ourOutputs[outputNum].keyPair = keyPair;
ourOutputs[outputNum].redeemScript = redeemScript;
ourOutputs[outputNum].redeemScript = p2wpkh.output;
ourOutputs[outputNum].amount = unspent.amount;
unspentAmountSatoshi += unspent.amount;
if (unspentAmountSatoshi >= amountToOutputSatoshi + feeInSatoshis) {
@ -92,23 +111,29 @@ exports.createHDSegwitTransaction = function(utxos, toAddress, amount, fixedFee,
// adding outputs
txb.addOutput(toAddress, amountToOutputSatoshi);
psbt.addOutput({
address: toAddress,
value: amountToOutputSatoshi,
});
if (amountToOutputSatoshi + feeInSatoshis < unspentAmountSatoshi) {
// sending less than we have, so the rest should go back
if (unspentAmountSatoshi - amountToOutputSatoshi - feeInSatoshis > 3 * feeInSatoshis) {
// to prevent @dust error change transferred amount should be at least 3xfee.
// if not - we just dont send change and it wil add to fee
txb.addOutput(changeAddress, unspentAmountSatoshi - amountToOutputSatoshi - feeInSatoshis);
psbt.addOutput({
address: changeAddress,
value: unspentAmountSatoshi - amountToOutputSatoshi - feeInSatoshis,
});
}
}
// now, signing every input with a corresponding key
for (let c = 0; c <= outputNum; c++) {
txb.sign(c, ourOutputs[c].keyPair, ourOutputs[c].redeemScript, null, ourOutputs[c].amount);
psbt.signInput(c, ourOutputs[c].keyPair);
}
let tx = txb.build();
let tx = psbt.finalizeAllInputs().extractTransaction();
return tx.toHex();
};
@ -120,37 +145,57 @@ exports.createSegwitTransaction = function(utxos, toAddress, amount, fixedFee, W
let feeInSatoshis = parseInt((fixedFee * 100000000).toFixed(0));
let keyPair = bitcoinjs.ECPair.fromWIF(WIF);
let pubKey = keyPair.getPublicKeyBuffer();
let pubKeyHash = bitcoinjs.crypto.hash160(pubKey);
let redeemScript = bitcoinjs.script.witnessPubKeyHash.output.encode(pubKeyHash);
let txb = new bitcoinjs.TransactionBuilder();
let p2wpkh = _p2wpkh({
pubkey: keyPair.publicKey,
});
let p2sh = _p2sh({
redeem: p2wpkh,
});
let psbt = new bitcoinjs.Psbt();
psbt.setVersion(1);
let unspentAmount = 0;
for (const unspent of utxos) {
if (unspent.confirmations < 2) {
// using only confirmed outputs
continue;
}
txb.addInput(unspent.txid, unspent.vout, sequence);
unspentAmount += parseInt((unspent.amount * 100000000).toFixed(0));
const satoshis = parseInt((unspent.amount * 100000000).toFixed(0));
psbt.addInput({
hash: unspent.txid,
index: unspent.vout,
sequence,
witnessUtxo: {
script: p2sh.output,
value: satoshis,
},
redeemScript: p2wpkh.output,
});
unspentAmount += satoshis;
}
let amountToOutput = parseInt(((amount - fixedFee) * 100000000).toFixed(0));
txb.addOutput(toAddress, amountToOutput);
psbt.addOutput({
address: toAddress,
value: amountToOutput,
});
if (amountToOutput + feeInSatoshis < unspentAmount) {
// sending less than we have, so the rest should go back
if (unspentAmount - amountToOutput - feeInSatoshis > 3 * feeInSatoshis) {
// to prevent @dust error change transferred amount should be at least 3xfee.
// if not - we just dont send change and it wil add to fee
txb.addOutput(changeAddress, unspentAmount - amountToOutput - feeInSatoshis);
psbt.addOutput({
address: changeAddress,
value: unspentAmount - amountToOutput - feeInSatoshis,
});
}
}
for (let c = 0; c < utxos.length; c++) {
txb.sign(c, keyPair, redeemScript, null, parseInt((utxos[c].amount * 100000000).toFixed(0)));
psbt.signInput(c, keyPair);
}
let tx = txb.build();
let tx = psbt.finalizeAllInputs().extractTransaction();
return tx.toHex();
};
@ -168,11 +213,33 @@ exports.createRBFSegwitTransaction = function(txhex, addressReplaceMap, feeDelta
highestSequence = i.sequence;
}
}
let keyPair = bitcoinjs.ECPair.fromWIF(WIF);
let p2wpkh = _p2wpkh({
pubkey: keyPair.publicKey,
});
let p2sh = _p2sh({
redeem: p2wpkh,
});
// creating TX
let txb = new bitcoinjs.TransactionBuilder();
let psbt = new bitcoinjs.Psbt();
psbt.setVersion(1);
for (let unspent of tx.ins) {
txb.addInput(unspent.hash.reverse().toString('hex'), unspent.index, highestSequence + 1);
let txid = Buffer.from(unspent.hash)
.reverse()
.toString('hex');
let index = unspent.index;
let amount = utxodata[txid][index];
psbt.addInput({
hash: txid,
index,
sequence: highestSequence + 1,
witnessUtxo: {
script: p2sh.output,
value: amount,
},
redeemScript: p2wpkh.output,
});
}
for (let o of tx.outs) {
@ -180,37 +247,36 @@ exports.createRBFSegwitTransaction = function(txhex, addressReplaceMap, feeDelta
if (addressReplaceMap[outAddress]) {
// means this is DESTINATION address, not messing with it's amount
// but replacing the address itseld
txb.addOutput(addressReplaceMap[outAddress], o.value);
psbt.addOutput({
address: addressReplaceMap[outAddress],
value: o.value,
});
} else {
// CHANGE address, so we deduct increased fee from here
let feeDeltaInSatoshi = parseInt((feeDelta * 100000000).toFixed(0));
txb.addOutput(outAddress, o.value - feeDeltaInSatoshi);
psbt.addOutput({
address: outAddress,
value: o.value - feeDeltaInSatoshi,
});
}
}
// signing
let keyPair = bitcoinjs.ECPair.fromWIF(WIF);
let pubKey = keyPair.getPublicKeyBuffer();
let pubKeyHash = bitcoinjs.crypto.hash160(pubKey);
let redeemScript = bitcoinjs.script.witnessPubKeyHash.output.encode(pubKeyHash);
for (let c = 0; c < tx.ins.length; c++) {
let txid = tx.ins[c].hash.reverse().toString('hex');
let index = tx.ins[c].index;
let amount = utxodata[txid][index];
txb.sign(c, keyPair, redeemScript, null, amount);
psbt.signInput(c, keyPair);
}
let newTx = txb.build();
let newTx = psbt.finalizeAllInputs().extractTransaction();
return newTx.toHex();
};
exports.generateNewSegwitAddress = function() {
let keyPair = bitcoinjs.ECPair.makeRandom();
let pubKey = keyPair.getPublicKeyBuffer();
let witnessScript = bitcoinjs.script.witnessPubKeyHash.output.encode(bitcoinjs.crypto.hash160(pubKey));
let scriptPubKey = bitcoinjs.script.scriptHash.output.encode(bitcoinjs.crypto.hash160(witnessScript));
let address = bitcoinjs.address.fromOutputScript(scriptPubKey);
let address = bitcoinjs.payments.p2sh({
redeem: bitcoinjs.payments.p2wpkh({
pubkey: keyPair.publicKey,
}),
}).address;
return {
address: address,
@ -235,10 +301,11 @@ exports.URI = function(paymentInfo) {
exports.WIF2segwitAddress = function(WIF) {
let keyPair = bitcoinjs.ECPair.fromWIF(WIF);
let pubKey = keyPair.getPublicKeyBuffer();
let witnessScript = bitcoinjs.script.witnessPubKeyHash.output.encode(bitcoinjs.crypto.hash160(pubKey));
let scriptPubKey = bitcoinjs.script.scriptHash.output.encode(bitcoinjs.crypto.hash160(witnessScript));
return bitcoinjs.address.fromOutputScript(scriptPubKey);
return bitcoinjs.payments.p2sh({
redeem: bitcoinjs.payments.p2wpkh({
pubkey: keyPair.publicKey,
}),
}).address;
};
exports.createTransaction = function(utxos, toAddress, _amount, _fixedFee, WIF, fromAddress) {
@ -246,6 +313,7 @@ exports.createTransaction = function(utxos, toAddress, _amount, _fixedFee, WIF,
let amountToOutput = toSatoshi(_amount - _fixedFee);
let pk = bitcoinjs.ECPair.fromWIF(WIF); // eslint-disable-line new-cap
let txb = new bitcoinjs.TransactionBuilder();
txb.setVersion(1);
let unspentAmount = 0;
for (const unspent of utxos) {
if (unspent.confirmations < 2) {
@ -263,7 +331,11 @@ exports.createTransaction = function(utxos, toAddress, _amount, _fixedFee, WIF,
}
for (let c = 0; c < utxos.length; c++) {
txb.sign(c, pk);
txb.sign({
prevOutScriptType: 'p2pkh',
vin: c,
keyPair: pk,
});
}
return txb.build().toHex();

63
package-lock.json

@ -2663,6 +2663,11 @@
"file-uri-to-path": "1.0.0"
}
},
"bip174": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/bip174/-/bip174-1.0.1.tgz",
"integrity": "sha512-Mq2aFs1TdMfxBpYPg7uzjhsiXbAtoVq44TNjEWtvuZBiBgc3m7+n55orYMtTAxdg7jWbL4DtH0MKocJER4xERQ=="
},
"bip21": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/bip21/-/bip21-2.0.2.tgz",
@ -2711,34 +2716,14 @@
"integrity": "sha512-pef6gxZFztEhaE9RY9HmWVmiIHqCb2OyS4HPKkpc6CIiiOa3Qmuoylxc5P2EkU3w+5eTSifI9SEZC88idAIGow=="
},
"bitcoinjs-lib": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-3.3.2.tgz",
"integrity": "sha512-l5qqvbaK8wwtANPf6oEffykycg4383XgEYdia1rI7/JpGf1jfRWlOUCvx5TiTZS7kyIvY4j/UhIQ2urLsvGkzw==",
"requires": {
"bech32": "^1.1.2",
"bigi": "^1.4.0",
"bip66": "^1.1.0",
"bitcoin-ops": "^1.3.0",
"bs58check": "^2.0.0",
"create-hash": "^1.1.0",
"create-hmac": "^1.1.3",
"ecurve": "^1.0.0",
"merkle-lib": "^2.0.10",
"pushdata-bitcoin": "^1.0.1",
"randombytes": "^2.0.1",
"safe-buffer": "^5.0.1",
"typeforce": "^1.11.3",
"varuint-bitcoin": "^1.0.4",
"wif": "^2.0.1"
}
},
"bitcoinjs5": {
"version": "git+https://github.com/Overtorment/bitcoinjs5.git#846c0185a6693f86540d58a7a5fffe8173dc8b34",
"from": "git+https://github.com/Overtorment/bitcoinjs5.git#846c018",
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-5.1.6.tgz",
"integrity": "sha512-NgvnA8XXUuzpuBnVs1plzZvVOYsuont4KPzaGcVIwjktYQbCk1hUkXnt4wujIOBscNsXuu+plVbPYvtMosZI/w==",
"requires": {
"@types/node": "10.12.18",
"bech32": "^1.1.2",
"bip32": "^2.0.3",
"bip174": "^1.0.1",
"bip32": "^2.0.4",
"bip66": "^1.1.0",
"bitcoin-ops": "^1.4.0",
"bs58check": "^2.0.0",
@ -2751,6 +2736,22 @@
"typeforce": "^1.11.3",
"varuint-bitcoin": "^1.0.4",
"wif": "^2.0.1"
},
"dependencies": {
"bip32": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/bip32/-/bip32-2.0.4.tgz",
"integrity": "sha512-ioPytarPDIrWckWMuK4RNUtvwhvWEc2fvuhnO0WEwu732k5OLjUXv4rXi2c/KJHw9ZMNQMkYRJrBw81RujShGQ==",
"requires": {
"@types/node": "10.12.18",
"bs58check": "^2.1.1",
"create-hash": "^1.2.0",
"create-hmac": "^1.1.7",
"tiny-secp256k1": "^1.1.0",
"typeforce": "^1.11.5",
"wif": "^2.0.6"
}
}
}
},
"bl": {
@ -10686,9 +10687,9 @@
}
},
"react-native-camera": {
"version": "2.11.1",
"resolved": "https://registry.npmjs.org/react-native-camera/-/react-native-camera-2.11.1.tgz",
"integrity": "sha512-ZmPZHcY7UXEf7Z8PoJX/WhFtmLFtdRPBGDwemOLlPRVwTVyu/OXVINDUWCug4daBqV8Fs3X1O6V927+K2u2GfA==",
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/react-native-camera/-/react-native-camera-3.4.0.tgz",
"integrity": "sha512-EFaixLtQsd3Kjs2AnxPx4Qb1mNSlZRZBVoxXDUdWcSdCEE3CSvdeonTb3vWAT3ZlGMi1RTOggtBpI4StMtFNjw==",
"requires": {
"prop-types": "^15.6.2"
}
@ -10769,9 +10770,9 @@
"integrity": "sha512-iuXBEZsQbKOe5uOLXuzTEvrvUCV0a4AktAfJHlWK7Jb0t/ABUc4kUfclqx+la+yA1TKrauDp/6MjrP0OUDXA9Q=="
},
"react-native-image-picker": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/react-native-image-picker/-/react-native-image-picker-0.28.1.tgz",
"integrity": "sha512-CW2dm+cjsdW2fjBW2WD/cSufNG0x0UpljwGHrjSzyB0TckoW+tjYv44UWtckCWxr1JtCg+QrYDO/MzlRyFcjwQ=="
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/react-native-image-picker/-/react-native-image-picker-1.1.0.tgz",
"integrity": "sha512-/KjHf4NNAjl6XM7FQuqvGDz1wB9sRdLf86+2yksLW/QTRR7CitX4TLCM8ZF9CX6Y0MsCTndkZia3zWE+nt/GiA=="
},
"react-native-level-fs": {
"version": "3.0.1",

10
package.json

@ -24,7 +24,7 @@
"rn-nodeify": "github:tradle/rn-nodeify"
},
"scripts": {
"prepare": "./patches/fix_mangle.sh; git apply patches/minifier.js.patch; git apply patches/minify.js.patch; git apply patches/transaction_builder.js.patch; git apply ./patches/transaction.js.patch",
"prepare": "./patches/fix_mangle.sh; git apply patches/minifier.js.patch; git apply patches/minify.js.patch",
"clean": "cd android/; ./gradlew clean; cd ..; rm -r -f /tmp/metro-cache/; rm -r -f node_modules/; npm cache clean --force; npm i; npm start -- --reset-cache",
"releasenotes2json": "./release-notes.sh > release-notes.txt; node -e 'console.log(JSON.stringify(require(\"fs\").readFileSync(\"release-notes.txt\", \"utf8\")));' > release-notes.json",
"podinstall": "./podinstall.sh",
@ -58,13 +58,13 @@
"bip21": "2.0.2",
"bip32": "2.0.3",
"bip39": "2.5.0",
"bitcoinjs-lib": "3.3.2",
"bitcoinjs5": "git+https://github.com/Overtorment/bitcoinjs5.git#846c018",
"bitcoinjs-lib": "^5.1.6",
"buffer": "5.2.1",
"buffer-reverse": "1.0.1",
"coinselect": "3.1.11",
"crypto-js": "3.1.9-1",
"dayjs": "1.8.14",
"ecurve": "^1.0.6",
"electrum-client": "git+https://github.com/BlueWallet/rn-electrum-client.git",
"eslint-config-prettier": "6.0.0",
"eslint-config-standard": "12.0.0",
@ -84,7 +84,7 @@
"react": "16.8.6",
"react-localization": "1.0.13",
"react-native": "0.60.5",
"react-native-camera": "2.11.1",
"react-native-camera": "3.4.0",
"react-native-device-info": "2.2.2",
"react-native-elements": "0.19.0",
"react-native-flexi-radio-button": "0.2.2",
@ -92,7 +92,7 @@
"react-native-gesture-handler": "1.3.0",
"react-native-handoff": "git+https://github.com/marcosrdz/react-native-handoff.git",
"react-native-haptic-feedback": "1.7.1",
"react-native-image-picker": "0.28.1",
"react-native-image-picker": "1.1.0",
"react-native-level-fs": "3.0.1",
"react-native-linear-gradient": "2.5.4",
"react-native-modal": "11.1.0",

12
patches/transaction.js.patch

@ -1,12 +0,0 @@
--- a/node_modules/bitcoinjs-lib/src/transaction.js 2018-07-18 00:17:03.540824839 +0100
+++ b/node_modules/bitcoinjs-lib/src/transaction.js 2018-07-18 00:24:55.840803782 +0100
@@ -408,7 +408,8 @@
Transaction.prototype.getId = function () {
// transaction hash's are displayed in reverse order
- return this.getHash().reverse().toString('hex')
+ var bufferReverse = require('buffer-reverse')
+ return bufferReverse(this.getHash()).toString('hex')
}
Transaction.prototype.toBuffer = function (buffer, initialOffset) {

12
patches/transaction_builder.js.patch

@ -1,12 +0,0 @@
--- a/node_modules/bitcoinjs-lib/src/transaction_builder.js 2018-07-18 00:09:22.924845375 +0100
+++ b/node_modules/bitcoinjs-lib/src/transaction_builder.js 2018-07-18 00:14:20.996832086 +0100
@@ -536,7 +536,8 @@
// is it a hex string?
if (typeof txHash === 'string') {
// transaction hashs's are displayed in reverse order, un-reverse it
- txHash = Buffer.from(txHash, 'hex').reverse()
+ var bufferReverse = require('buffer-reverse')
+ txHash = bufferReverse(new Buffer(txHash, 'hex'))
// is it a Transaction object?
} else if (txHash instanceof Transaction) {

21
screen/selftest.js

@ -3,9 +3,9 @@ import { ScrollView, View } from 'react-native';
import { BlueLoading, BlueSpacing20, SafeBlueArea, BlueCard, BlueText, BlueNavigationStyle } from '../BlueComponents';
import PropTypes from 'prop-types';
import { SegwitP2SHWallet, LegacyWallet, HDSegwitP2SHWallet, HDSegwitBech32Wallet } from '../class';
const bitcoin = require('bitcoinjs-lib');
let BigNumber = require('bignumber.js');
let encryption = require('../encryption');
let bitcoin = require('bitcoinjs-lib');
let BlueElectrum = require('../BlueElectrum');
export default class Selftest extends Component {
@ -166,7 +166,7 @@ export default class Selftest extends Component {
block_height: 514991,
tx_input_n: -1,
tx_output_n: 2,
value: 546,
value: 110000,
ref_balance: 546,
spent: false,
confirmations: 9,
@ -185,7 +185,7 @@ export default class Selftest extends Component {
}
if (
tx !==
'0100000000010123c3e0950eabb2b5c1a956e9d003c516fc29ff529211bd552be719fb78ea5e0f0200000017160014597ce022baa887799951e0496c769d9cc0c759dc0000000001a0860100000000001976a914ff715fb722cb10646d80709aeac7f2f4ee00278f88ac0247304402202507d6b05ab19c7fdee217e97fddab80d481d7b2a103c00cecfc634bf897188d02205fa62ad413b6e441f99f94d7d8f9cd4ba51a1d928cbdec6873fa915236dd6d92012103aea0dfd576151cb399347aa6732f8fdf027b9ea3ea2e65fb754803f776e0a50900000000'
'0100000000010123c3e0950eabb2b5c1a956e9d003c516fc29ff529211bd552be719fb78ea5e0f0200000017160014597ce022baa887799951e0496c769d9cc0c759dc0000000001a0860100000000001976a914ff715fb722cb10646d80709aeac7f2f4ee00278f88ac02473044022075670317a0e5b5d4eef154b03db97396a64cbc6ef3b576d98367e1a83c1c488002206d6df1e8085fd711d6ea264de3803340f80fa2c6e30683879d9ad40f3228c56c012103aea0dfd576151cb399347aa6732f8fdf027b9ea3ea2e65fb754803f776e0a50900000000'
) {
errorMessage += 'created tx hex doesnt match; ';
isOk = false;
@ -217,16 +217,17 @@ export default class Selftest extends Component {
let mnemonic =
'honey risk juice trip orient galaxy win situate shoot anchor bounce remind horse traffic exotic since escape mimic ramp skin judge owner topple erode';
let seed = bip39.mnemonicToSeed(mnemonic);
let root = bitcoin.HDNode.fromSeedBuffer(seed);
let root = bitcoin.bip32.fromSeed(seed);
let path = "m/49'/0'/0'/0/0";
let child = root.derivePath(path);
let keyhash = bitcoin.crypto.hash160(child.getPublicKeyBuffer());
let scriptSig = bitcoin.script.witnessPubKeyHash.output.encode(keyhash);
let addressBytes = bitcoin.crypto.hash160(scriptSig);
let outputScript = bitcoin.script.scriptHash.output.encode(addressBytes);
let address = bitcoin.address.fromOutputScript(outputScript, bitcoin.networks.bitcoin);
let address = bitcoin.payments.p2sh({
redeem: bitcoin.payments.p2wpkh({
pubkey: child.publicKey,
network: bitcoin.networks.bitcoin,
}),
network: bitcoin.networks.bitcoin,
}).address;
if (address !== '3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK') {
errorMessage += 'bip49 is not ok; ';

2
screen/send/details.js

@ -37,6 +37,7 @@ import BitcoinBIP70TransactionDecode from '../../bip70/bip70';
import { BitcoinUnit, Chain } from '../../models/bitcoinUnits';
import { HDLegacyP2PKHWallet, HDSegwitBech32Wallet, HDSegwitP2SHWallet, LightningCustodianWallet } from '../../class';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
const bitcoin = require('bitcoinjs-lib');
import { BitcoinTransaction } from '../../models/bitcoinTransactionInfo';
const bip21 = require('bip21');
let BigNumber = require('bignumber.js');
@ -44,7 +45,6 @@ const { width } = Dimensions.get('window');
/** @type {AppStorage} */
let BlueApp = require('../../BlueApp');
let loc = require('../../loc');
let bitcoin = require('bitcoinjs-lib');
const btcAddressRx = /^[a-zA-Z0-9]{26,35}$/;

60
screen/send/scanQrAddress.js

@ -1,25 +1,25 @@
/* global alert */
import React from 'react';
import { Image, TouchableOpacity } from 'react-native';
import PropTypes from 'prop-types';
import { RNCamera } from 'react-native-camera';
import { SafeBlueArea } from '../../BlueComponents';
import { Icon } from 'react-native-elements';
import ImagePicker from 'react-native-image-picker';
const LocalQRCode = require('@remobile/react-native-qrcode-local-image');
export default class CameraExample extends React.Component {
export default class ScanQRCode extends React.Component {
static navigationOptions = {
header: null,
};
state = {
isLoading: false,
};
cameraRef = null;
onBarCodeScanned = ret => {
if (this.state.isLoading) return;
this.setState({ isLoading: true }, () => {
onBarCodeRead = ret => {
if (RNCamera.Constants.CameraStatus === RNCamera.Constants.CameraStatus.READY) this.cameraRef.pausePreview();
const onBarScannedProp = this.props.navigation.getParam('onBarScanned');
this.props.navigation.goBack();
onBarScannedProp(ret.data);
});
}; // end
render() {
@ -33,8 +33,9 @@ export default class CameraExample extends React.Component {
buttonPositive: 'OK',
buttonNegative: 'Cancel',
}}
ref={ref => (this.cameraRef = ref)}
style={{ flex: 1, justifyContent: 'space-between' }}
onBarCodeRead={this.onBarCodeScanned}
onBarCodeRead={this.onBarCodeRead}
barCodeTypes={[RNCamera.Constants.BarCodeType.qr]}
/>
<TouchableOpacity
@ -52,12 +53,51 @@ export default class CameraExample extends React.Component {
>
<Image style={{ alignSelf: 'center' }} source={require('../../img/close-white.png')} />
</TouchableOpacity>
<TouchableOpacity
style={{
width: 40,
height: 40,
backgroundColor: '#FFFFFF',
justifyContent: 'center',
borderRadius: 20,
position: 'absolute',
left: 24,
bottom: 48,
}}
onPress={() => {
if (RNCamera.Constants.CameraStatus === RNCamera.Constants.CameraStatus.READY) this.cameraRef.pausePreview();
ImagePicker.launchImageLibrary(
{
title: null,
mediaType: 'photo',
takePhotoButtonTitle: null,
},
response => {
if (response.uri) {
const uri = response.uri.toString().replace('file://', '');
LocalQRCode.decode(uri, (error, result) => {
if (!error) {
this.onBarCodeRead({ data: result });
} else {
if (RNCamera.Constants.CameraStatus === RNCamera.Constants.CameraStatus.READY) this.cameraRef.resumePreview();
alert('The selected image does not contain a QR Code.');
}
});
} else {
if (RNCamera.Constants.CameraStatus === RNCamera.Constants.CameraStatus.READY) this.cameraRef.resumePreview();
}
},
);
}}
>
<Icon name="image" type="font-awesome" color="#0c2550" />
</TouchableOpacity>
</SafeBlueArea>
);
}
}
CameraExample.propTypes = {
ScanQRCode.propTypes = {
navigation: PropTypes.shape({
goBack: PropTypes.func,
dismiss: PropTypes.func,

13
screen/settings/language.js

@ -18,13 +18,15 @@ export default class Language extends Component {
language: loc.getLanguage(),
availableLanguages: [
{ label: 'English', value: 'en' },
{ label: 'Česky (CZ)', value: 'cs_cz' },
{ label: 'Afrikaans (AFR)', value: 'zar_afr' },
{ label: 'Chinese (ZH)', value: 'zh_cn' },
{ label: 'Chinese (TW)', value: 'zh_tw' },
{ label: 'Croatian (HR)', value: 'hr_hr' },
{ label: 'Česky (CZ)', value: 'cs_cz' },
{ label: 'Danish (DK)', value: 'da_dk' },
{ label: 'Norsk (NB)', value: 'nb_no' },
{ label: 'Deutsch (DE)', value: 'de_de' },
{ label: 'Español (ES)', value: 'es' },
{ label: 'Ελληνικά (EL)', value: 'el' },
{ label: 'Italiano (IT)', value: 'it' },
{ label: 'Suomi (FI)', value: 'fi_fi' },
{ label: 'Français (FR)', value: 'fr_fr' },
@ -32,15 +34,16 @@ export default class Language extends Component {
{ label: 'Magyar (HU)', value: 'hu_hu' },
{ label: '日本語 (JP)', value: 'jp_jp' },
{ label: 'Nederlands (NL)', value: 'nl_nl' },
{ label: 'Portuguese (BR)', value: 'pt_br' },
{ label: 'Portuguese (PT)', value: 'pt_pt' },
{ label: 'Norsk (NB)', value: 'nb_no' },
{ label: 'Português (BR)', value: 'pt_br' },
{ label: 'Português (PT)', value: 'pt_pt' },
{ label: 'Русский', value: 'ru' },
{ label: 'Svenska (SE)', value: 'sv_se' },
{ label: 'Thai (TH)', value: 'th_th' },
{ label: 'Vietnamese (VN)', value: 'vi_vn' },
{ label: 'Українська', value: 'ua' },
{ label: 'Ελληνικά (EL)', value: 'el' },
{ label: 'Türkçe (TR)', value: 'tr_tr' },
{ label: 'Xhosa (XHO)', value: 'zar_xho' },
],
};
}

2
screen/transactions/RBF-create.js

@ -14,8 +14,8 @@ import {
BlueNavigationStyle,
} from '../../BlueComponents';
import PropTypes from 'prop-types';
const bitcoinjs = require('bitcoinjs-lib');
let BigNumber = require('bignumber.js');
let bitcoinjs = require('bitcoinjs-lib');
let BlueApp = require('../../BlueApp');
export default class SendCreate extends Component {

20
screen/wallets/scanQrWif.js

@ -21,18 +21,10 @@ export default class ScanQrWif extends React.Component {
header: null,
};
state = {
isLoading: false,
};
state = { isLoading: false };
onBarCodeScanned = async ret => {
if (+new Date() - this.lastTimeIveBeenHere < 6000) {
this.lastTimeIveBeenHere = +new Date();
return;
}
this.lastTimeIveBeenHere = +new Date();
this.setState({ isLoading: true });
if (RNCamera.Constants.CameraStatus === RNCamera.Constants.CameraStatus.READY) this.cameraRef.pausePreview();
if (ret.data[0] === '6') {
// password-encrypted, need to ask for password and decrypt
console.log('trying to decrypt...');
@ -55,6 +47,7 @@ export default class ScanQrWif extends React.Component {
ret.data = wif.encode(0x80, decryptedKey.privateKey, decryptedKey.compressed);
} catch (e) {
console.log(e.message);
if (RNCamera.Constants.CameraStatus === RNCamera.Constants.CameraStatus.READY) this.cameraRef.resumePreview();
this.setState({ message: false, isLoading: false });
return alert(loc.wallets.scanQrWif.bad_password);
}
@ -66,6 +59,7 @@ export default class ScanQrWif extends React.Component {
if (w.getSecret() === ret.data) {
// lookig for duplicates
this.setState({ isLoading: false });
if (RNCamera.Constants.CameraStatus === RNCamera.Constants.CameraStatus.READY) this.cameraRef.resumePreview();
return alert(loc.wallets.scanQrWif.wallet_already_exists); // duplicate, not adding
}
}
@ -78,6 +72,7 @@ export default class ScanQrWif extends React.Component {
if (w.getSecret() === hd.getSecret()) {
// lookig for duplicates
this.setState({ isLoading: false });
if (RNCamera.Constants.CameraStatus === RNCamera.Constants.CameraStatus.READY) this.cameraRef.resumePreview();
return alert(loc.wallets.scanQrWif.wallet_already_exists); // duplicate, not adding
}
}
@ -105,6 +100,7 @@ export default class ScanQrWif extends React.Component {
if (w.getSecret() === hd.getSecret()) {
// lookig for duplicates
this.setState({ isLoading: false });
if (RNCamera.Constants.CameraStatus === RNCamera.Constants.CameraStatus.READY) this.cameraRef.resumePreview();
return alert(loc.wallets.scanQrWif.wallet_already_exists); // duplicate, not adding
}
}
@ -131,6 +127,7 @@ export default class ScanQrWif extends React.Component {
if (w.getSecret() === hd.getSecret()) {
// lookig for duplicates
this.setState({ isLoading: false });
if (RNCamera.Constants.CameraStatus === RNCamera.Constants.CameraStatus.READY) this.cameraRef.resumePreview();
return alert(loc.wallets.scanQrWif.wallet_already_exists); // duplicate, not adding
}
}
@ -169,6 +166,7 @@ export default class ScanQrWif extends React.Component {
} catch (Err) {
console.log(Err);
this.setState({ isLoading: false });
if (RNCamera.Constants.CameraStatus === RNCamera.Constants.CameraStatus.READY) this.cameraRef.resumePreview();
alert(Err.message);
return;
}
@ -218,6 +216,7 @@ export default class ScanQrWif extends React.Component {
if (newWallet.getAddress() === false && newLegacyWallet.getAddress() === false) {
alert(loc.wallets.scanQrWif.bad_wif);
if (RNCamera.Constants.CameraStatus === RNCamera.Constants.CameraStatus.READY) this.cameraRef.resumePreview();
this.setState({ isLoading: false });
return;
}
@ -303,6 +302,7 @@ export default class ScanQrWif extends React.Component {
}}
style={{ flex: 1, justifyContent: 'space-between' }}
onBarCodeRead={this.onBarCodeScanned}
ref={ref => (this.cameraRef = ref)}
barCodeTypes={[RNCamera.Constants.BarCodeType.qr]}
/>
<TouchableOpacity

2
tests/integration/Electrum.test.js

@ -1,8 +1,8 @@
/* global it, describe, afterAll, beforeAll, jasmine */
const bitcoin = require('bitcoinjs-lib');
global.net = require('net');
let BlueElectrum = require('../../BlueElectrum');
let assert = require('assert');
let bitcoin = require('bitcoinjs-lib');
jasmine.DEFAULT_TIMEOUT_INTERVAL = 150 * 1000;
afterAll(() => {

17
tests/integration/HDWallet.test.js

@ -1,9 +1,9 @@
/* global it, jasmine, afterAll, beforeAll */
import { SegwitP2SHWallet, SegwitBech32Wallet, HDSegwitP2SHWallet, HDLegacyBreadwalletWallet, HDLegacyP2PKHWallet } from '../../class';
import { BitcoinUnit } from '../../models/bitcoinUnits';
const bitcoin = require('bitcoinjs-lib');
global.crypto = require('crypto'); // shall be used by tests under nodejs CLI, but not in RN environment
let assert = require('assert');
let bitcoin = require('bitcoinjs-lib');
global.net = require('net'); // needed by Electrum client. For RN it is proviced in shim.js
let BlueElectrum = require('../../BlueElectrum'); // so it connects ASAP
jasmine.DEFAULT_TIMEOUT_INTERVAL = 300 * 1000;
@ -155,10 +155,8 @@ it('HD (BIP49) can create TX', async () => {
assert.strictEqual(tx.outs.length, 2);
assert.strictEqual(tx.outs[0].value, 500);
assert.strictEqual(tx.outs[1].value, 25400);
let chunksIn = bitcoin.script.decompile(tx.outs[0].script);
let toAddress = bitcoin.address.fromOutputScript(chunksIn);
chunksIn = bitcoin.script.decompile(tx.outs[1].script);
let changeAddress = bitcoin.address.fromOutputScript(chunksIn);
let toAddress = bitcoin.address.fromOutputScript(tx.outs[0].script);
let changeAddress = bitcoin.address.fromOutputScript(tx.outs[1].script);
assert.strictEqual('3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK', toAddress);
assert.strictEqual(hd._getInternalAddressByIndex(hd.next_free_change_address_index), changeAddress);
@ -175,8 +173,7 @@ it('HD (BIP49) can create TX', async () => {
tx = bitcoin.Transaction.fromHex(txhex);
assert.strictEqual(tx.ins.length, 1);
assert.strictEqual(tx.outs.length, 1);
chunksIn = bitcoin.script.decompile(tx.outs[0].script);
toAddress = bitcoin.address.fromOutputScript(chunksIn);
toAddress = bitcoin.address.fromOutputScript(tx.outs[0].script);
assert.strictEqual('3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK', toAddress);
// testing sendMAX
@ -349,10 +346,8 @@ it('Legacy HD (BIP44) can create TX', async () => {
assert.strictEqual(tx.outs.length, 2);
assert.strictEqual(tx.outs[0].value, 80000); // payee
assert.strictEqual(tx.outs[1].value, 19500); // change
let chunksIn = bitcoin.script.decompile(tx.outs[0].script);
let toAddress = bitcoin.address.fromOutputScript(chunksIn);
chunksIn = bitcoin.script.decompile(tx.outs[1].script);
let changeAddress = bitcoin.address.fromOutputScript(chunksIn);
let toAddress = bitcoin.address.fromOutputScript(tx.outs[0].script);
let changeAddress = bitcoin.address.fromOutputScript(tx.outs[1].script);
assert.strictEqual('3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK', toAddress);
assert.strictEqual(hd._getInternalAddressByIndex(hd.next_free_change_address_index), changeAddress);

2
tests/integration/hd-segwit-bech32-transaction.test.js

@ -1,6 +1,6 @@
/* global it, describe, jasmine, afterAll, beforeAll */
import { HDSegwitBech32Wallet, HDSegwitBech32Transaction, SegwitBech32Wallet } from '../../class';
const bitcoin = require('bitcoinjs5');
const bitcoin = require('bitcoinjs-lib');
global.crypto = require('crypto'); // shall be used by tests under nodejs CLI, but not in RN environment
let assert = require('assert');
global.net = require('net'); // needed by Electrum client. For RN it is proviced in shim.js

6
tests/unit/signer.js

@ -1,5 +1,5 @@
/* global describe, it */
let bitcoinjs = require('bitcoinjs-lib')
const bitcoinjs = require('bitcoinjs-lib');
let assert = require('assert')
describe('unit - signer', function () {
@ -30,8 +30,8 @@ describe('unit - signer', function () {
let txhex = '0100000000010115b7e9d1f6b8164a0e95544a94f5b0fbfaadc35f8415acd0ec0e58d5ce8c1a1e0100000017160014f90e5bca5635b84bd828064586bd7eb117fee9a90000000002905f0100000000001976a914f7c6c1f9f6142107ed293c8fbf85fbc49eb5f1b988ace00f97000000000017a9146fbf1cee74734503297e46a0db3e3fbb06f2e9d38702483045022100bd687693e57161282a80affb82f18386cbf319bca72ca2c16320b0f3b087bee802205e22a9a16b86628ea08eab83aebec1348c476e9d0c90cd41aa73c47f50d86aab0121039425479ea581ebc7f55959da8c2e1a1063491768860386335dd4630b5eeacfc500000000'
let signer = require('../../models/signer')
let dummyUtxodata = {
'15b7e9d1f6b8164a0e95544a94f5b0fbfaadc35f8415acd0ec0e58d5ce8c1a1e': { // txid we use output from
1: 666 // output index and it's value in satoshi
'1e1a8cced5580eecd0ac15845fc3adfafbb0f5944a54950e4a16b8f6d1e9b715': { // txid we use output from
1: 10000000 // output index and it's value in satoshi
}
}
let newhex = signer.createRBFSegwitTransaction(txhex, {'1Pb81K1xJnMjUfFgKUbva6gr1HCHXxHVnr': '3BDsBDxDimYgNZzsqszNZobqQq3yeUoJf2'}, 0.0001, 'KyWpryAKPiXXbipxWhtprZjSLVjp22sxbVnJssq2TCNQxs1SuMeD', dummyUtxodata)

Loading…
Cancel
Save