ADD: support for Cobo Vault hardware wallet

This commit is contained in:
Overtorment 2020-06-24 12:56:35 +01:00 committed by GitHub
parent 1d89bff1f3
commit 82fd28ce12
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 901 additions and 23 deletions

View file

@ -34,6 +34,8 @@ import { BlurView } from '@react-native-community/blur';
import showPopupMenu from 'react-native-popup-menu-android';
import NetworkTransactionFees, { NetworkTransactionFeeType } from './models/networkTransactionFees';
import Biometric from './class/biometrics';
import { encodeUR } from 'bc-ur/dist';
import QRCode from 'react-native-qrcode-svg';
const loc = require('./loc/');
/** @type {AppStorage} */
const BlueApp = require('./BlueApp');
@ -2529,3 +2531,151 @@ export function BlueBigCheckmark({ style }) {
</View>
);
}
export class DynamicQRCode extends Component {
constructor() {
super();
const qrCodeHeight = height > width ? width - 40 : width / 3;
const qrCodeMaxHeight = 370;
this.state = {
index: 0,
total: 0,
qrCodeHeight: Math.min(qrCodeHeight, qrCodeMaxHeight),
intervalHandler: null,
};
}
fragments = [];
componentDidMount() {
const { value, capacity = 800 } = this.props;
this.fragments = encodeUR(value, capacity);
this.setState(
{
total: this.fragments.length,
},
() => {
this.startAutoMove();
},
);
}
moveToNextFragment = () => {
const { index, total } = this.state;
if (index === total - 1) {
this.setState({
index: 0,
});
} else {
this.setState(state => ({
index: state.index + 1,
}));
}
};
startAutoMove = () => {
if (!this.state.intervalHandler)
this.setState(() => ({
intervalHandler: setInterval(this.moveToNextFragment, 500),
}));
};
stopAutoMove = () => {
clearInterval(this.state.intervalHandler);
this.setState(() => ({
intervalHandler: null,
}));
};
moveToPreviousFragment = () => {
const { index, total } = this.state;
if (index > 0) {
this.setState(state => ({
index: state.index - 1,
}));
} else {
this.setState(state => ({
index: total - 1,
}));
}
};
render() {
const currentFragment = this.fragments[this.state.index];
return currentFragment ? (
<View style={animatedQRCodeStyle.container}>
<BlueSpacing20 />
<View style={[animatedQRCodeStyle.qrcodeContainer, { height: this.state.qrCodeHeight }]}>
<QRCode
value={currentFragment.toUpperCase()}
size={this.state.qrCodeHeight}
color={BlueApp.settings.foregroundColor}
logoBackgroundColor={BlueApp.settings.brandingColor}
ecl="L"
/>
</View>
<BlueSpacing20 />
<View>
<Text style={animatedQRCodeStyle.text}>
{this.state.index + 1} of {this.state.total}
</Text>
</View>
<BlueSpacing20 />
<View style={animatedQRCodeStyle.controller}>
<TouchableOpacity
style={[animatedQRCodeStyle.button, { width: '25%', alignItems: 'flex-start' }]}
onPress={this.moveToPreviousFragment}
>
<Text style={animatedQRCodeStyle.text}>Previous</Text>
</TouchableOpacity>
<TouchableOpacity
style={[animatedQRCodeStyle.button, { width: '50%' }]}
onPress={this.state.intervalHandler ? this.stopAutoMove : this.startAutoMove}
>
<Text style={animatedQRCodeStyle.text}>{this.state.intervalHandler ? 'Stop' : 'Start'}</Text>
</TouchableOpacity>
<TouchableOpacity
style={[animatedQRCodeStyle.button, { width: '25%', alignItems: 'flex-end' }]}
onPress={this.moveToNextFragment}
>
<Text style={animatedQRCodeStyle.text}>Next</Text>
</TouchableOpacity>
</View>
</View>
) : (
<View>
<Text>Initialing</Text>
</View>
);
}
}
const animatedQRCodeStyle = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
alignItems: 'center',
},
qrcodeContainer: {
alignItems: 'center',
justifyContent: 'center',
},
controller: {
width: '90%',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
backgroundColor: '#ccddf9',
borderRadius: 25,
height: 45,
paddingHorizontal: 18,
},
button: {
alignItems: 'center',
height: 45,
justifyContent: 'center',
},
text: {
fontSize: 16,
},
});

View file

@ -0,0 +1,36 @@
# bc-bech32
this library is for implementing [BlockChain Commons bc-32](https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-0004-bc32.md)
an encoding
## Installation
```
yarn add bc-bech32
```
## Test
```
yarn test
```
## Build
```
yarn build
```
## Sample
```
import { encodeBc32Data, decodeBc32Data, encodeSegwitAddress, decodeSegwitAddress } from '../src';
const data = encodeBc32Data('48656c6c6f20776f726c64');
console.log(data) // fpjkcmr0ypmk7unvvsh4ra4j
```
note: for using the node vesion should be upper than 10.16
this library is inspire on the [bech32](https://github.com/sipa/bech32/tree/master/ref/javascript). Thanks for the good library.

136
blue_modules/bc-bech32/dist/bech32.js vendored Normal file
View file

@ -0,0 +1,136 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var index_1 = {};
var Bech32Version;
(function (Bech32Version) {
Bech32Version[Bech32Version["Origin"] = 1] = "Origin";
Bech32Version[Bech32Version["bis"] = 2] = "bis";
})(Bech32Version = Bech32Version || (Bech32Version = {}));
index_1.Bech32Version=Bech32Version;
var CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';
var GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];
function polymod(values) {
var chk = 1;
for (var p = 0; p < values.length; ++p) {
var top_1 = chk >> 25;
chk = ((chk & 0x1ffffff) << 5) ^ values[p];
for (var i = 0; i < 6; ++i) {
if ((top_1 >> i) & 1) {
chk ^= GENERATOR[i];
}
}
}
return chk;
}
function hrpExpand(hrp) {
var ret = [];
var p;
for (p = 0; p < hrp.length; ++p) {
ret.push(hrp.charCodeAt(p) >> 5);
}
ret.push(0);
for (p = 0; p < hrp.length; ++p) {
ret.push(hrp.charCodeAt(p) & 31);
}
return ret;
}
function verifyChecksum(hrp, data, version) {
var header;
if (hrp) {
header = hrpExpand(hrp);
}
else {
header = [0];
}
var check = version === index_1.Bech32Version.Origin ? 1 : 0x3fffffff;
return polymod(header.concat(data)) === check;
}
function createChecksum(hrp, data, bech32Version) {
var values;
if (hrp) {
values = hrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]);
}
else {
values = [0].concat(data).concat([0, 0, 0, 0, 0, 0]);
}
var chk = bech32Version === index_1.Bech32Version.Origin ? 1 : 0x3fffffff;
var mod = polymod(values) ^ chk;
var ret = [];
for (var p = 0; p < 6; ++p) {
ret.push((mod >> (5 * (5 - p))) & 31);
}
return ret;
}
var encode = function (hrp, data, version) {
var combined = data.concat(createChecksum(hrp, data, version));
var ret;
if (hrp) {
ret = hrp + '1';
}
else {
ret = '';
}
for (var p = 0; p < combined.length; ++p) {
ret += CHARSET.charAt(combined[p]);
}
return ret;
};
var decodeBc32 = function (bechString) {
var data = [];
for (var p = 0; p < bechString.length; ++p) {
var d = CHARSET.indexOf(bechString.charAt(p));
if (d === -1) {
return null;
}
data.push(d);
}
if (!verifyChecksum(null, data, index_1.Bech32Version.bis)) {
return null;
}
return { hrp: null, data: data.slice(0, data.length - 6) };
};
var decode = function (bechString) {
var p;
var hasLower = false;
var hasUpper = false;
for (p = 0; p < bechString.length; ++p) {
if (bechString.charCodeAt(p) < 33 || bechString.charCodeAt(p) > 126) {
return null;
}
if (bechString.charCodeAt(p) >= 97 && bechString.charCodeAt(p) <= 122) {
hasLower = true;
}
if (bechString.charCodeAt(p) >= 65 && bechString.charCodeAt(p) <= 90) {
hasUpper = true;
}
}
if (hasLower && hasUpper) {
return null;
}
bechString = bechString.toLowerCase();
var pos = bechString.lastIndexOf('1');
if (pos === -1) {
return decodeBc32(bechString);
}
if (pos < 1 || pos + 7 > bechString.length || bechString.length > 90) {
return null;
}
var hrp = bechString.substring(0, pos);
var data = [];
for (p = pos + 1; p < bechString.length; ++p) {
var d = CHARSET.indexOf(bechString.charAt(p));
if (d === -1) {
return null;
}
data.push(d);
}
if (!verifyChecksum(hrp, data, index_1.Bech32Version.Origin)) {
return null;
}
return { hrp: hrp, data: data.slice(0, data.length - 6) };
};
exports.default = {
encode: encode,
decode: decode,
};
//# sourceMappingURL=bech32.js.map

75
blue_modules/bc-bech32/dist/index.js vendored Normal file
View file

@ -0,0 +1,75 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.decodeBc32Data = exports.encodeBc32Data = exports.encodeSegwitAddress = exports.decodeSegwitAddress = exports.Bech32Version = void 0;
var bech32_1 = __importDefault(require("./bech32"));
var Bech32Version;
(function (Bech32Version) {
Bech32Version[Bech32Version["Origin"] = 1] = "Origin";
Bech32Version[Bech32Version["bis"] = 2] = "bis";
})(Bech32Version = exports.Bech32Version || (exports.Bech32Version = {}));
var convertBits = function (data, fromBits, toBits, pad) {
var acc = 0;
var bits = 0;
var ret = [];
var maxv = (1 << toBits) - 1;
for (var p = 0; p < data.length; ++p) {
var value = data[p];
if (value < 0 || value >> fromBits !== 0) {
return null;
}
acc = (acc << fromBits) | value;
bits += fromBits;
while (bits >= toBits) {
bits -= toBits;
ret.push((acc >> bits) & maxv);
}
}
if (pad) {
if (bits > 0) {
ret.push((acc << (toBits - bits)) & maxv);
}
}
else if (bits >= fromBits || (acc << (toBits - bits)) & maxv) {
return null;
}
return ret;
};
exports.decodeSegwitAddress = function (hrp, addr) {
var dec = bech32_1.default.decode(addr);
if (dec === null || dec.hrp !== hrp || dec.data.length < 1 || dec.data[0] > 16) {
return null;
}
var res = convertBits(Uint8Array.from(dec.data.slice(1)), 5, 8, false);
if (res === null || res.length < 2 || res.length > 40) {
return null;
}
if (dec.data[0] === 0 && res.length !== 20 && res.length !== 32) {
return null;
}
return { version: dec.data[0], program: res };
};
exports.encodeSegwitAddress = function (hrp, version, program) {
var ret = bech32_1.default.encode(hrp, [version].concat(convertBits(program, 8, 5, true)), Bech32Version.Origin);
if (exports.decodeSegwitAddress(hrp, ret) === null) {
return null;
}
return ret;
};
exports.encodeBc32Data = function (hex) {
var data = Buffer.from(hex, 'hex');
return bech32_1.default.encode(null, convertBits(data, 8, 5, true), Bech32Version.bis);
};
exports.decodeBc32Data = function (data) {
var result = bech32_1.default.decode(data);
if (result) {
var res = convertBits(Buffer.from(result.data), 5, 8, false);
return Buffer.from(res).toString('hex');
}
else {
return null;
}
};
//# sourceMappingURL=index.js.map

View file

@ -0,0 +1,64 @@
{
"_args": [
[
"bc-bech32@1.0.2",
"/home/user/BlueWallet"
]
],
"_from": "bc-bech32@1.0.2",
"_id": "bc-bech32@1.0.2",
"_inBundle": false,
"_integrity": "sha512-lwAn5R4LUhcnyrZgNx3YdDPr5+nseM4kARANcv8i0YOMtnPJRTF7B7TZzS3DYgC6tff/aR2W/3jGoY/SJMs6MA==",
"_location": "/bc-bech32",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "bc-bech32@1.0.2",
"name": "bc-bech32",
"escapedName": "bc-bech32",
"rawSpec": "1.0.2",
"saveSpec": null,
"fetchSpec": "1.0.2"
},
"_requiredBy": [
"/bc-ur"
],
"_resolved": "https://registry.npmjs.org/bc-bech32/-/bc-bech32-1.0.2.tgz",
"_spec": "1.0.2",
"_where": "/home/user/BlueWallet",
"description": "this library is for implementing [BlockChain Commons bc-32](https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-0004-bc32.md)",
"files": [
"src",
"dist"
],
"gitHead": "981882405f62b1268edcbda018662a369431bb62",
"license": "MIT",
"main": "dist/index.js",
"name": "bc-bech32",
"scripts": {
"build": "npm run clean && npx tsc -p tsconfig.json -outDir ./dist",
"clean": "rm -rf ./dist"
},
"version": "1.0.2",
"react-native": {
"path": "path-browserify",
"fs": "react-native-level-fs",
"_stream_transform": "readable-stream/transform",
"_stream_readable": "readable-stream/readable",
"_stream_writable": "readable-stream/writable",
"_stream_duplex": "readable-stream/duplex",
"_stream_passthrough": "readable-stream/passthrough",
"stream": "stream-browserify"
},
"browser": {
"path": "path-browserify",
"fs": "react-native-level-fs",
"_stream_transform": "readable-stream/transform",
"_stream_readable": "readable-stream/readable",
"_stream_writable": "readable-stream/writable",
"_stream_duplex": "readable-stream/duplex",
"_stream_passthrough": "readable-stream/passthrough",
"stream": "stream-browserify"
}
}

View file

@ -0,0 +1,22 @@
# bc-ur
This library is an implementation of [bc-ur](https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-0005-ur.md).
Still in development, please do not use it in production;
## Install
```
yarn add bc-ur
```
## Usage
```
const {encodeUR, decodeUR} from 'bc-ur';
const hexstr = '7e4a61385e2550981b4b5633ab178eb077a30505fbd53f107ec1081e7cf0ca3c0dc0bfea5b8bfb5e6ffc91afd104c3aa756210b5dbc5118fd12c87ee04269815ba6a9968a0d0d3b7a9b631382a36bc70ab626d5670b4b48ff843f4d9a15631aa67c7aaf0ac6ce7e3bff03b2c9643e3375e47493c4e0f8635329d66fdec41b10ce74dcbf25fc15d829e7830c325643a98561f441b40a02e8353493e6afc16192fe99d90d8ca65539af77ddeaccc8943a37563a9ba83675bd5d4da7c60c9a172cf6940cbf0ec8fe04175a629932e3512c5d2aaea3cca3246f40a21ffdc33c3987dc7b880351230eb3759fe3c7dc7b2d3a20a95996ff0b7a0dba834f96beb64c14e3426fb051a936ba41569ab99c0066a6d9c0777a49e49e6cbad24d722a4c7da112432679264b9adc0a8cff9dd1fe0ee9ee2747f6a68537c389a7303a1af23c534ee6392bc17b04cf0fbce7689e66b673a440c04a9454005b0c76664639113458eb7d0902eff04d11138ce2a8ee16a9cd7c8926514efa9bd83ae7a4c139835f0fe0f68c628e0645c8524c30dfc314e825a7aa13224d98e2f7a9d12183a999bb1f28549c99a9072d99c05c24e0c84848c4fc147a094ab7b69e9cbea86952fccf15500fbb234ffe6ee6e6ded515c8016cb017ba36fb931ef276cec4ed22c1aed1495d2df3b3ce66c03f5b9ffa8434bf0e8fb149de94e050b3da178df1f76c00a366cb2801fabdf1a1e90cd3cd45ecb7a930a40b151455f76b726d552f31c21324992da257ff8bde2923dfd5d0d6b87233fae215ffacbecd96249099e7e3427d533db56cdb09c7475b4ce3314e33f43953a7370866cc11d85f00b71b15510b46c4b4fa490c660ddfeda0ceb1b8265995f7071c155ad1b57465fdc0fa81a73f9f19ac4872029d5844c1838f732e803043673e26cbc5b51297a324ff00a2d2d4222bad556b93d27c8e376e3ff8f9d37b3073410708ebb3d4dd7473d27212310b71a3c33a5c8f87f44824640e7f8970f4eda9364195c87a91172b8f085f1773641dde1ce21938746234055bc971ce2325f814e3eec60f781dd4faf52afd5be4a6b38656f7e9739f724cb7ccd4e4d01e802add3dc7b83191f894b3ee0ed752ee514d5ec55';
const fragments = encodeUR(hexstr, 500);
const payload = decodeUR(fragments);
```

115
blue_modules/bc-ur/dist/decodeUR.js vendored Normal file
View file

@ -0,0 +1,115 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.extractSingleWorkload = exports.decodeUR = void 0;
var utils_1 = require("./utils");
var miniCbor_1 = require("./miniCbor");
var bc_bech32_1 = require("bc-bech32");
var checkAndGetSequence = function (sequence) {
var pieces = sequence.toUpperCase().split('OF');
if (pieces.length !== 2)
throw new Error("invalid sequence: " + sequence);
var index = pieces[0], total = pieces[1];
return [+index, +total];
};
var checkDigest = function (digest, payload) {
if (bc_bech32_1.decodeBc32Data(digest) !== utils_1.sha256Hash(Buffer.from(bc_bech32_1.decodeBc32Data(payload), 'hex')).toString('hex')) {
throw new Error("invalid digest: \n digest:" + digest + " \n payload:" + payload);
}
};
var checkURHeader = function (UR, type) {
if (type === void 0) { type = 'bytes'; }
if (UR.toUpperCase() !== ("ur:" + type).toUpperCase())
throw new Error("invalid UR header: " + UR);
};
var dealWithSingleWorkload = function (workload, type) {
if (type === void 0) { type = 'bytes'; }
var pieces = workload.split('/');
switch (pieces.length) {
case 2: {
//UR:Type/[Fragment]
checkURHeader(pieces[0], type);
return pieces[1];
}
case 3: {
//UR:Type/[Digest]/[Fragment] when Sequencing is omitted, Digest MAY be omitted;
//should check digest
checkURHeader(pieces[0], type);
var digest = pieces[1];
var fragment = pieces[2];
checkDigest(digest, fragment);
return fragment;
}
case 4: {
//UR:Type/[Sequencing]/[Digest]/[Fragment]
//should check sequencing and digest
checkURHeader(pieces[0], type);
checkAndGetSequence(pieces[1]);
var digest = pieces[2];
var fragment = pieces[3];
checkDigest(digest, fragment);
return fragment;
}
default:
throw new Error("invalid workload pieces length: expect 2 / 3 / 4 bug got " + pieces.length);
}
};
var dealWithMultipleWorkloads = function (workloads, type) {
if (type === void 0) { type = 'bytes'; }
var length = workloads.length;
var fragments = new Array(length).fill('');
var digest = '';
workloads.forEach(function (workload) {
var pieces = workload.split('/');
checkURHeader(pieces[0], type);
var _a = checkAndGetSequence(pieces[1]), index = _a[0], total = _a[1];
if (total !== length)
throw new Error("invalid workload: " + workload + ", total " + total + " not equal workloads length " + length);
if (digest && digest !== pieces[2])
throw new Error("invalid workload: " + workload + ", checksum changed " + digest + ", " + pieces[2]);
digest = pieces[2];
if (fragments[index - 1])
throw new Error("invalid workload: " + workload + ", index " + index + " has already been set");
fragments[index - 1] = pieces[3];
});
var payload = fragments.join('');
checkDigest(digest, payload);
return payload;
};
var getBC32Payload = function (workloads, type) {
if (type === void 0) { type = 'bytes'; }
try {
var length_1 = workloads.length;
if (length_1 === 1) {
return dealWithSingleWorkload(workloads[0], type);
}
else {
return dealWithMultipleWorkloads(workloads, type);
}
}
catch (e) {
throw new Error("invalid workloads: " + workloads + "\n " + e);
}
};
exports.decodeUR = function (workloads, type) {
if (type === void 0) { type = 'bytes'; }
var bc32Payload = getBC32Payload(workloads, type);
var cborPayload = bc_bech32_1.decodeBc32Data(bc32Payload);
return miniCbor_1.decodeSimpleCBOR(cborPayload);
};
exports.extractSingleWorkload = function (workload) {
var pieces = workload.toUpperCase().split('/');
switch (pieces.length) {
case 2: //UR:Type/[Fragment]
case 3: {
//UR:Type/[Digest]/[Fragment] when Sequencing is omitted, Digest MAY be omitted;
return [1, 1];
}
case 4: {
//UR:Type/[Sequencing]/[Digest]/[Fragment]
return checkAndGetSequence(pieces[1]);
}
default:
throw new Error("invalid workload pieces length: expect 2 / 3 / 4 bug got " + pieces.length);
}
};
//# sourceMappingURL=decodeUR.js.map

37
blue_modules/bc-ur/dist/encodeUR.js vendored Normal file
View file

@ -0,0 +1,37 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.encodeUR = void 0;
var miniCbor_1 = require("./miniCbor");
var bc_bech32_1 = require("bc-bech32");
var utils_1 = require("./utils");
var composeUR = function (payload, type) {
if (type === void 0) { type = 'bytes'; }
return "ur:" + type + "/" + payload;
};
var composeDigest = function (payload, digest) {
return digest + "/" + payload;
};
var composeSequencing = function (payload, index, total) {
return index + 1 + "of" + total + "/" + payload;
};
var composeHeadersToFragments = function (fragments, digest, type) {
if (type === void 0) { type = 'bytes'; }
if (fragments.length === 1) {
return [composeUR(fragments[0])];
}
else {
return fragments.map(function (f, index) {
return utils_1.compose3(function (payload) { return composeUR(payload, type); }, function (payload) { return composeSequencing(payload, index, fragments.length); }, function (payload) { return composeDigest(payload, digest); })(f);
});
}
};
exports.encodeUR = function (payload, fragmentCapacity) {
if (fragmentCapacity === void 0) { fragmentCapacity = 200; }
var cborPayload = miniCbor_1.encodeSimpleCBOR(payload);
var bc32Payload = bc_bech32_1.encodeBc32Data(cborPayload);
var digest = utils_1.sha256Hash(Buffer.from(cborPayload, 'hex')).toString('hex');
var bc32Digest = bc_bech32_1.encodeBc32Data(digest);
var fragments = bc32Payload.match(new RegExp('.{1,' + fragmentCapacity + '}', 'g'));
return composeHeadersToFragments(fragments, bc32Digest, 'bytes');
};
//# sourceMappingURL=encodeUR.js.map

8
blue_modules/bc-ur/dist/index.js vendored Normal file
View file

@ -0,0 +1,8 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var encodeUR_1 = require("./encodeUR");
Object.defineProperty(exports, "encodeUR", { enumerable: true, get: function () { return encodeUR_1.encodeUR; } });
var decodeUR_1 = require("./decodeUR");
Object.defineProperty(exports, "decodeUR", { enumerable: true, get: function () { return decodeUR_1.decodeUR; } });
Object.defineProperty(exports, "extractSingleWorkload", { enumerable: true, get: function () { return decodeUR_1.extractSingleWorkload; } });
//# sourceMappingURL=index.js.map

62
blue_modules/bc-ur/dist/miniCbor.js vendored Normal file
View file

@ -0,0 +1,62 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.decodeSimpleCBOR = exports.encodeSimpleCBOR = exports.composeHeader = void 0;
/*
this an simple cbor implementation which is just using
on BCR-05
*/
exports.composeHeader = function (length) {
var header;
if (length > 0 && length <= 23) {
header = Buffer.from([0x40 + length]);
}
if (length >= 24 && length <= 255) {
var headerLength = Buffer.alloc(1);
headerLength.writeUInt8(length);
header = Buffer.concat([Buffer.from([0x58]), headerLength]);
}
if (length >= 256 && length <= 65535) {
var headerLength = Buffer.alloc(2);
headerLength.writeUInt16BE(length);
header = Buffer.concat([Buffer.from([0x59]), headerLength]);
}
if (length >= 65536 && length <= Math.pow(2, 32) - 1) {
var headerLength = Buffer.alloc(4);
headerLength.writeUInt32BE(length);
header = Buffer.concat([Buffer.from([0x60]), headerLength]);
}
return header;
};
exports.encodeSimpleCBOR = function (data) {
var bufferData = Buffer.from(data, 'hex');
if (bufferData.length <= 0 || bufferData.length >= Math.pow(2, 32)) {
throw new Error('data is too large');
}
var header = exports.composeHeader(bufferData.length);
var endcoded = Buffer.concat([header, bufferData]);
return endcoded.toString('hex');
};
exports.decodeSimpleCBOR = function (data) {
var dataBuffer = Buffer.from(data, 'hex');
if (dataBuffer.length <= 0) {
throw new Error('input data is not valid');
}
var header = dataBuffer[0];
if (header < 0x58) {
var dataLength = header - 0x40;
return dataBuffer.slice(1, 1 + dataLength).toString('hex');
}
if (header == 0x58) {
var dataLength = dataBuffer.slice(1, 2).readUInt8();
return dataBuffer.slice(2, 2 + dataLength).toString('hex');
}
if (header == 0x59) {
var dataLength = dataBuffer.slice(1, 3).readUInt16BE();
return dataBuffer.slice(3, 3 + dataLength).toString('hex');
}
if (header == 0x60) {
var dataLength = dataBuffer.slice(1, 5).readUInt32BE();
return dataBuffer.slice(5, 5 + dataLength).toString('hex');
}
};
//# sourceMappingURL=miniCbor.js.map

11
blue_modules/bc-ur/dist/utils.js vendored Normal file
View file

@ -0,0 +1,11 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.compose3 = exports.sha256Hash = void 0;
var bitcoinjs_lib_1 = require("bitcoinjs-lib");
exports.sha256Hash = function (data) {
return bitcoinjs_lib_1.crypto.sha256(data);
};
exports.compose3 = function (f, g, h) { return function (x) {
return f(g(h(x)));
}; };
//# sourceMappingURL=utils.js.map

View file

@ -0,0 +1,81 @@
{
"_args": [
[
"bc-ur@0.1.6",
"/home/user/BlueWallet"
]
],
"_from": "bc-ur@0.1.6",
"_id": "bc-ur@0.1.6",
"_inBundle": false,
"_integrity": "sha512-k5jZLNgiCMQH5d/4lwsa6DJjH12vzdTEr9qVH1y9UPzJW32Ga1u8iC0KDAqtYnkvh8NR4DW8Fco6D2hphHZLzg==",
"_location": "/bc-ur",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "bc-ur@0.1.6",
"name": "bc-ur",
"escapedName": "bc-ur",
"rawSpec": "0.1.6",
"saveSpec": null,
"fetchSpec": "0.1.6"
},
"_requiredBy": [
"/"
],
"_resolved": "https://registry.npmjs.org/bc-ur/-/bc-ur-0.1.6.tgz",
"_spec": "0.1.6",
"_where": "/home/user/BlueWallet",
"author": {
"name": "aaronisme",
"email": "aarondongchen@gmail.com"
},
"bugs": {
"url": "https://github.com/CoboVault/cobo-vault-blockchain-base/issues"
},
"description": "BlockChain Commons Uniform Resources",
"directories": {
"lib": "src",
"test": "__tests__"
},
"files": [
"src",
"dist"
],
"gitHead": "2b6ffe86287ae1a58def5d8e485abc413fe1558f",
"homepage": "https://github.com/CoboVault/cobo-vault-blockchain-base#readme",
"license": "MIT",
"main": "dist/index.js",
"name": "bc-ur",
"repository": {
"type": "git",
"url": "git+https://github.com/CoboVault/cobo-vault-blockchain-base.git"
},
"scripts": {
"build": "npm run clean && npx tsc -p tsconfig.json -outDir ./dist",
"clean": "rm -rf ./dist",
"test": "echo \"Error: run tests from root\" && exit 1"
},
"version": "0.1.6",
"react-native": {
"path": "path-browserify",
"fs": "react-native-level-fs",
"_stream_transform": "readable-stream/transform",
"_stream_readable": "readable-stream/readable",
"_stream_writable": "readable-stream/writable",
"_stream_duplex": "readable-stream/duplex",
"_stream_passthrough": "readable-stream/passthrough",
"stream": "stream-browserify"
},
"browser": {
"path": "path-browserify",
"fs": "react-native-level-fs",
"_stream_transform": "readable-stream/transform",
"_stream_readable": "readable-stream/readable",
"_stream_writable": "readable-stream/writable",
"_stream_duplex": "readable-stream/duplex",
"_stream_passthrough": "readable-stream/passthrough",
"stream": "stream-browserify"
}
}

View file

@ -1,5 +1,6 @@
/* global alert */
import {
AppStorage,
SegwitP2SHWallet,
LegacyWallet,
WatchOnlyWallet,
@ -16,8 +17,7 @@ import {
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
const EV = require('../events');
const A = require('../analytics');
/** @type {AppStorage} */
const BlueApp = require('../BlueApp');
const BlueApp: AppStorage = require('../BlueApp');
const loc = require('../loc');
const bip38 = require('../blue_modules/bip38');
const wif = require('wif');
@ -38,8 +38,9 @@ export default class WalletImport {
alert('This wallet has been previously imported.');
WalletImport.removePlaceholderWallet();
} else {
const emptyWalletLabel = new LegacyWallet().getLabel();
ReactNativeHapticFeedback.trigger('notificationSuccess', { ignoreAndroidSystemSettings: false });
w.setLabel(loc.wallets.import.imported + ' ' + w.typeReadable);
if (w.getLabel() === emptyWalletLabel) w.setLabel(loc.wallets.import.imported + ' ' + w.typeReadable);
w.setUserHasSavedExport(true);
if (additionalProperties) {
for (const [key, value] of Object.entries(additionalProperties)) {

View file

@ -153,6 +153,13 @@ export class AbstractWallet {
this.secret = parsedSecret.keystore.xpub;
this.masterFingerprint = masterFingerprint;
}
// It is a Cobo Vault Hardware Wallet
if (parsedSecret && parsedSecret.ExtPubKey && parsedSecret.MasterFingerprint) {
this.secret = parsedSecret.ExtPubKey;
const mfp = Buffer.from(parsedSecret.MasterFingerprint, 'hex').reverse().toString('hex');
this.masterFingerprint = parseInt(mfp, 16);
this.setLabel('Cobo Vault ' + parsedSecret.MasterFingerprint);
}
} catch (_) {}
return this;
}

View file

@ -372,9 +372,9 @@ PODS:
- React
- RNWatch (0.5.0):
- React
- Sentry (5.1.4):
- Sentry/Core (= 5.1.4)
- Sentry/Core (5.1.4)
- Sentry (5.1.5):
- Sentry/Core (= 5.1.5)
- Sentry/Core (5.1.5)
- swift_qrcodejs (1.1.2)
- ToolTipMenu (5.2.0):
- React
@ -682,7 +682,7 @@ SPEC CHECKSUMS:
RNSVG: ce9d996113475209013317e48b05c21ee988d42e
RNVectorIcons: 0bb4def82230be1333ddaeee9fcba45f0b288ed4
RNWatch: 4de37878bbf071847e438b089c01d4ad8d4eddae
Sentry: 74f2ad93a20a5567a1badaedf5b3f60b142ad082
Sentry: e73af5462d1e7978c571aa2fdb861053938d7efd
swift_qrcodejs: 4d024fc98b0778b804ec6a5c810880fd092aec9d
ToolTipMenu: 4d89d95ddffd7539230bdbe02ee51bbde362e37e
Yoga: 3ebccbdd559724312790e7742142d062476b698e

View file

@ -101,7 +101,6 @@ dayjs.extend(relativeTime);
dayjs.locale(lang.split('_')[0]);
}
} else {
console.log(RNLocalize.getLocales());
const locales = RNLocalize.getLocales();
if (Object.keys(AvailableLanguages).some(language => language === locales[0])) {
strings.saveLanguage(locales[0].languageCode);

8
package-lock.json generated
View file

@ -5901,6 +5901,14 @@
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
},
"bc-bech32": {
"version": "file:blue_modules/bc-bech32",
"integrity": "sha512-lwAn5R4LUhcnyrZgNx3YdDPr5+nseM4kARANcv8i0YOMtnPJRTF7B7TZzS3DYgC6tff/aR2W/3jGoY/SJMs6MA=="
},
"bc-ur": {
"version": "file:blue_modules/bc-ur",
"integrity": "sha512-k5jZLNgiCMQH5d/4lwsa6DJjH12vzdTEr9qVH1y9UPzJW32Ga1u8iC0KDAqtYnkvh8NR4DW8Fco6D2hphHZLzg=="
},
"bcrypt-pbkdf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",

View file

@ -70,6 +70,8 @@
"@sentry/react-native": "1.4.5",
"amplitude-js": "5.11.0",
"assert": "1.5.0",
"bc-ur": "file:blue_modules/bc-ur",
"bc-bech32": "file:blue_modules/bc-bech32",
"bech32": "1.1.3",
"bignumber.js": "9.0.0",
"bip21": "2.0.2",

View file

@ -14,7 +14,6 @@ import {
PermissionsAndroid,
StyleSheet,
} from 'react-native';
import QRCode from 'react-native-qrcode-svg';
import Clipboard from '@react-native-community/clipboard';
import {
BlueButton,
@ -25,6 +24,7 @@ import {
BlueSpacing20,
BlueCopyToClipboardButton,
BlueBigCheckmark,
DynamicQRCode,
} from '../../BlueComponents';
import PropTypes from 'prop-types';
import Share from 'react-native-share';
@ -32,6 +32,8 @@ import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import { RNCamera } from 'react-native-camera';
import RNFS from 'react-native-fs';
import DocumentPicker from 'react-native-document-picker';
import { decodeUR, extractSingleWorkload } from 'bc-ur/dist';
import { Psbt } from 'bitcoinjs-lib';
const loc = require('../../loc');
const EV = require('../../events');
const BlueElectrum = require('../../BlueElectrum');
@ -122,9 +124,63 @@ export default class PsbtWithHardwareWallet extends Component {
cameraRef = null;
_onReadUniformResource = ur => {
try {
const [index, total] = extractSingleWorkload(ur);
const { animatedQRCodeData } = this.state;
if (animatedQRCodeData.length > 0) {
const currentTotal = animatedQRCodeData[0].total;
if (total !== currentTotal) {
alert('invalid animated QRCode');
this.setState({ renderScanner: false });
}
}
if (!animatedQRCodeData.find(i => i.index === index)) {
this.setState(
state => ({
animatedQRCodeData: [
...state.animatedQRCodeData,
{
index,
total,
data: ur,
},
],
}),
() => {
if (this.state.animatedQRCodeData.length === total) {
this.setState(
{
renderScanner: false,
},
() => {
const payload = decodeUR(this.state.animatedQRCodeData.map(i => i.data));
const psbtB64 = Buffer.from(payload, 'hex').toString('base64');
const psbt = Psbt.fromBase64(psbtB64);
this.setState({ txhex: psbt.extractTransaction().toHex() });
},
);
}
},
);
}
} catch (Err) {
alert('invalid animated QRCode fragment, please try again');
}
};
_combinePSBT = receivedPSBT => {
return this.state.fromWallet.combinePsbt(
this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64(),
receivedPSBT,
);
};
onBarCodeRead = ret => {
if (RNCamera.Constants.CameraStatus === RNCamera.Constants.CameraStatus.READY) this.cameraRef.pausePreview();
if (ret.data.toUpperCase().startsWith('UR')) {
return this._onReadUniformResource(ret.data);
}
if (ret.data.indexOf('+') === -1 && ret.data.indexOf('=') === -1 && ret.data.indexOf('=') === -1) {
// this looks like NOT base64, so maybe its transaction's hex
this.setState({ renderScanner: false, txhex: ret.data });
@ -133,10 +189,7 @@ export default class PsbtWithHardwareWallet extends Component {
this.setState({ renderScanner: false }, () => {
try {
const Tx = this.state.fromWallet.combinePsbt(
this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64(),
ret.data,
);
const Tx = this._combinePSBT(ret.data);
this.setState({ txhex: Tx.toHex() });
} catch (Err) {
alert(Err);
@ -157,6 +210,7 @@ export default class PsbtWithHardwareWallet extends Component {
isSecondPSBTAlreadyBase64: false,
deepLinkPSBT: undefined,
txhex: props.route.params.txhex || undefined,
animatedQRCodeData: [],
};
this.fileName = `${Date.now()}.psbt`;
}
@ -245,7 +299,7 @@ export default class PsbtWithHardwareWallet extends Component {
<SafeBlueArea style={styles.root}>
<BlueBigCheckmark style={styles.blueBigCheckmark} />
<BlueCard>
<BlueButton onPress={this.props.navigation.dangerouslyGetParent().pop} title={loc.send.success.done} />
<BlueButton onPress={() => this.props.navigation.dangerouslyGetParent().pop()} title={loc.send.success.done} />
</BlueCard>
</SafeBlueArea>
);
@ -343,13 +397,7 @@ export default class PsbtWithHardwareWallet extends Component {
This is partially signed bitcoin transaction (PSBT). Please finish signing it with your hardware wallet.
</BlueText>
<BlueSpacing20 />
<QRCode
value={this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64()}
size={this.state.qrCodeHeight}
color={BlueApp.settings.foregroundColor}
logoBackgroundColor={BlueApp.settings.brandingColor}
ecl="L"
/>
<DynamicQRCode value={this.state.psbt.toHex()} capacity={200} />
<BlueSpacing20 />
<BlueButton
icon={{
@ -357,7 +405,7 @@ export default class PsbtWithHardwareWallet extends Component {
type: 'font-awesome',
color: BlueApp.settings.buttonTextColor,
}}
onPress={() => this.setState({ renderScanner: true })}
onPress={() => this.setState({ renderScanner: true, animatedQRCodeData: [] })}
title="Scan Signed Transaction"
/>
<BlueSpacing20 />

View file

@ -103,6 +103,22 @@ describe('Watch only wallet', () => {
);
});
it('can import cobo vault JSON skeleton wallet', async () => {
const skeleton =
'{"ExtPubKey":"zpub6rcabYFcdr41zyUNRWRyHYs2Sm86E5XV8RjjRzTFYsiCngteeZnkwaF2xuhjmM6kpHjuNpFW42BMhzPmFwXt48e1FhddMB7xidZzN4SF24K","MasterFingerprint":"5271c071","CoboVaultFirmwareVersion":"1.2.4(BTC-Only)"}';
const w = new WatchOnlyWallet();
w.setSecret(skeleton);
w.init();
assert.ok(w.valid());
assert.strictEqual(
w.getSecret(),
'zpub6rcabYFcdr41zyUNRWRyHYs2Sm86E5XV8RjjRzTFYsiCngteeZnkwaF2xuhjmM6kpHjuNpFW42BMhzPmFwXt48e1FhddMB7xidZzN4SF24K',
);
assert.strictEqual(w.getMasterFingerprint(), 1908437330);
assert.strictEqual(w.getMasterFingerprintHex(), '5271c071');
assert.strictEqual(w.getLabel(), 'Cobo Vault 5271c071');
});
it('can combine signed PSBT and prepare it for broadcast', async () => {
const w = new WatchOnlyWallet();
w.setSecret('zpub6rjLjQVqVnj7crz9E4QWj4WgczmEseJq22u2B6k2HZr6NE2PQx3ZYg8BnbjN9kCfHymSeMd2EpwpM5iiz5Nrb3TzvddxW2RMcE3VXdVaXHk');