ADD: airdrop ms cosigner (#5682)

This commit is contained in:
S.Blagogee 2023-09-17 18:28:54 +02:00 committed by GitHub
parent 8ebbdcc459
commit cc728a01c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 163 additions and 24 deletions

18
App.js
View File

@ -58,8 +58,15 @@ if (Platform.OS === 'android') {
}
const App = () => {
const { walletsInitialized, wallets, addWallet, saveToDisk, fetchAndSaveWalletTransactions, refreshAllWalletTransactions } =
useContext(BlueStorageContext);
const {
walletsInitialized,
wallets,
addWallet,
saveToDisk,
fetchAndSaveWalletTransactions,
refreshAllWalletTransactions,
setSharedCosigner,
} = useContext(BlueStorageContext);
const appState = useRef(AppState.currentState);
const clipboardContent = useRef();
const colorScheme = useColorScheme();
@ -328,7 +335,12 @@ const App = () => {
};
const handleOpenURL = event => {
DeeplinkSchemaMatch.navigationRouteFor(event, value => NavigationService.navigate(...value), { wallets, addWallet, saveToDisk });
DeeplinkSchemaMatch.navigationRouteFor(event, value => NavigationService.navigate(...value), {
wallets,
addWallet,
saveToDisk,
setSharedCosigner,
});
};
const showClipboardAlert = ({ contentType }) => {

View File

@ -30,6 +30,7 @@ export const BlueStorageProvider = ({ children }) => {
const [isElectrumDisabled, setIsElectrumDisabled] = useState(true);
const [isTorDisabled, setIsTorDisabled] = useState(false);
const [isPrivacyBlurEnabled, setIsPrivacyBlurEnabled] = useState(true);
const [currentSharedCosigner, setCurrentSharedCosigner] = useState('');
useEffect(() => {
BlueElectrum.isDisabled().then(setIsElectrumDisabled);
@ -200,6 +201,10 @@ export const BlueStorageProvider = ({ children }) => {
setWallets([...BlueApp.getWallets()]);
};
const setSharedCosigner = cosigner => {
setCurrentSharedCosigner(cosigner);
};
let txMetadata = BlueApp.tx_metadata || {};
const getTransactions = BlueApp.getTransactions;
const isAdvancedModeEnabled = BlueApp.isAdvancedModeEnabled;
@ -239,6 +244,8 @@ export const BlueStorageProvider = ({ children }) => {
setSelectedWallet,
addWallet,
deleteWallet,
currentSharedCosigner,
setSharedCosigner,
addAndSaveWallet,
setItem,
getItem,

View File

@ -31,7 +31,11 @@ class DeeplinkSchemaMatch {
* @param event {{url: string}} URL deeplink as passed to app, e.g. `bitcoin:bc1qh6tf004ty7z7un2v5ntu4mkf630545gvhs45u7?amount=666&label=Yo`
* @param completionHandler {function} Callback that returns [string, params: object]
*/
static navigationRouteFor(event, completionHandler, context = { wallets: [], saveToDisk: () => {}, addWallet: () => {} }) {
static navigationRouteFor(
event,
completionHandler,
context = { wallets: [], saveToDisk: () => {}, addWallet: () => {}, setSharedCosigner: () => {} },
) {
if (event.url === null) {
return;
}
@ -104,6 +108,17 @@ class DeeplinkSchemaMatch {
})
.catch(e => console.warn(e));
return;
} else if (event.url.endsWith('.json')) {
RNFS.readFile(decodeURI(event.url))
.then(file => {
// checks whether the necessary json keys are present in order to set a cosigner,
// doesn't validate the values this happens later
if (!file || !this.hasNeededJsonKeysForMultiSigSharing(file)) {
return;
}
context.setSharedCosigner(file);
})
.catch(e => console.warn(e));
}
let isBothBitcoinAndLightning;
try {
@ -377,6 +392,20 @@ class DeeplinkSchemaMatch {
return text.startsWith('widget?action=');
}
static hasNeededJsonKeysForMultiSigSharing(str) {
let obj;
// Check if it's a valid JSON
try {
obj = JSON.parse(str);
} catch (e) {
return false;
}
// Check for the existence and type of the keys
return typeof obj.xfp === 'string' && typeof obj.xpub === 'string' && typeof obj.path === 'string';
}
static isBothBitcoinAndLightning(url) {
if (url.includes('lightning') && (url.includes('bitcoin') || url.includes('BITCOIN'))) {
const txInfo = url.split(/(bitcoin:\/\/|BITCOIN:\/\/|bitcoin:|BITCOIN:|lightning:|lightning=|bitcoin=)+/);

View File

@ -56,6 +56,18 @@
<string>io.bluewallet.backup</string>
</array>
</dict>
<dict>
<key>CFBundleTypeName</key>
<string>JSON File</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSItemContentTypes</key>
<array>
<string>public.json</string>
</array>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
@ -251,6 +263,29 @@
</array>
<key>UTImportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.text</string>
</array>
<key>UTTypeDescription</key>
<string>JSON File</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>public.json</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>json</string>
</array>
<key>public.mime-type</key>
<array>
<string>application/json</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeConformsTo</key>
<array>

View File

@ -482,6 +482,8 @@
"header": "Send",
"share": "Share",
"view": "View",
"shared_key_detected": "Shared cosigner",
"shared_key_detected_question": "A cosigner was shared with you, do you want to import it?",
"manage_keys": "Manage Keys",
"how_many_signatures_can_bluewallet_make": "how many signatures can BlueWallet make",
"signatures_required_to_spend": "Signatures required {number}",

View File

@ -41,6 +41,7 @@ import { BlueStorageContext } from '../../blue_modules/storage-context';
import { encodeUR } from '../../blue_modules/ur';
import QRCodeComponent from '../../components/QRCodeComponent';
import alert from '../../components/Alert';
import confirm from '../../helpers/confirm';
const prompt = require('../../helpers/prompt');
const A = require('../../blue_modules/analytics');
@ -49,7 +50,8 @@ const isDesktop = getSystemName() === 'Mac OS X';
const staticCache = {};
const WalletsAddMultisigStep2 = () => {
const { addWallet, saveToDisk, isElectrumDisabled, isAdvancedModeEnabled, sleep } = useContext(BlueStorageContext);
const { addWallet, saveToDisk, isElectrumDisabled, isAdvancedModeEnabled, sleep, currentSharedCosigner, setSharedCosigner } =
useContext(BlueStorageContext);
const { colors } = useTheme();
const navigation = useNavigation();
@ -75,6 +77,20 @@ const WalletsAddMultisigStep2 = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
console.log(currentSharedCosigner);
if (currentSharedCosigner) {
(async function () {
if (await confirm(loc.multisig.shared_key_detected, loc.multisig.shared_key_detected_question)) {
setImportText(currentSharedCosigner);
setIsProvideMnemonicsModalVisible(true);
setSharedCosigner('');
}
})();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentSharedCosigner]);
const handleOnHelpPress = () => {
navigation.navigate('WalletsAddMultisigHelp');
};
@ -244,7 +260,7 @@ const WalletsAddMultisigStep2 = () => {
setIsProvideMnemonicsModalVisible(true);
};
const tryUsingXpub = async xpub => {
const tryUsingXpub = async (xpub, fp, path) => {
if (!MultisigHDWallet.isXpubForMultisig(xpub)) {
setIsProvideMnemonicsModalVisible(false);
setIsLoading(false);
@ -253,25 +269,31 @@ const WalletsAddMultisigStep2 = () => {
alert(loc.multisig.not_a_multisignature_xpub);
return;
}
let fp;
try {
fp = await prompt(loc.multisig.input_fp, loc.multisig.input_fp_explain, true, 'plain-text');
fp = (fp + '').toUpperCase();
if (!MultisigHDWallet.isFpValid(fp)) fp = '00000000';
} catch {
return setIsLoading(false);
if (fp) {
// do nothing, it's already set
} else {
try {
fp = await prompt(loc.multisig.input_fp, loc.multisig.input_fp_explain, true, 'plain-text');
fp = (fp + '').toUpperCase();
if (!MultisigHDWallet.isFpValid(fp)) fp = '00000000';
} catch (e) {
return setIsLoading(false);
}
}
let path;
try {
path = await prompt(
loc.multisig.input_path,
loc.formatString(loc.multisig.input_path_explain, { default: getPath() }),
true,
'plain-text',
);
if (!MultisigHDWallet.isPathValid(path)) path = getPath();
} catch {
return setIsLoading(false);
if (path) {
// do nothing, it's already set
} else {
try {
path = await prompt(
loc.multisig.input_path,
loc.formatString(loc.multisig.input_path_explain, { default: getPath() }),
true,
'plain-text',
);
if (!MultisigHDWallet.isPathValid(path)) path = getPath();
} catch {
return setIsLoading(false);
}
}
setIsProvideMnemonicsModalVisible(false);
@ -291,6 +313,20 @@ const WalletsAddMultisigStep2 = () => {
if (MultisigHDWallet.isXpubValid(importText)) {
return tryUsingXpub(importText);
}
try {
const jsonText = JSON.parse(importText);
let fp;
let path;
if (jsonText.xpub) {
if (jsonText.xfp) {
fp = jsonText.xfp;
}
if (jsonText.path) {
path = jsonText.path;
}
return tryUsingXpub(jsonText.xpub, fp, path);
}
} catch {}
const hd = new HDSegwitBech32Wallet();
hd.setSecret(importText);
if (!hd.validateMnemonic()) {

View File

@ -462,4 +462,22 @@ describe.each(['', '//'])('unit - DeepLinkSchemaMatch', function (suffix) {
assert.strictEqual(DeeplinkSchemaMatch.getUrlFromSetLndhubUrlAction('gsom?url=https%3A%2F%2Flndhub.herokuapp.com%3A443'), false);
assert.strictEqual(DeeplinkSchemaMatch.getUrlFromSetLndhubUrlAction('sdfhserhsthsd'), false);
});
it('should accept only the one valid format', function () {
// has all the necessary json keys
const isAllowed1 = '{"xfp":"ffffffff", "path":"m/84\'/0\'/0\'", "xpub":"Zpubsnkjansdjnjnekjwcnwkjnc"}';
// has all the necessary json keys, different order
const isAllowed2 = '{"path":"m/84\'/0\'/0\'", "xpub":"Zpubsnkjansdjnjnekjwcnwkjnc", "xfp":"ffffffff"}';
//
assert.strictEqual(DeeplinkSchemaMatch.hasNeededJsonKeysForMultiSigSharing(isAllowed1), true);
assert.strictEqual(DeeplinkSchemaMatch.hasNeededJsonKeysForMultiSigSharing(isAllowed2), true);
const isNotAllowed1 = '{"path":"m/84\'/0\'/0\'", "xpub":"Zpubsnkjansdjnjnekjwcnwkjnc"}';
const isNotAllowed2 = '{"path":1233, "xpub":"Zpubsnkjansdjnjnekjwcnwkjnc", "xfp":"ffffffff"}';
assert.strictEqual(DeeplinkSchemaMatch.hasNeededJsonKeysForMultiSigSharing(isNotAllowed1), false);
assert.strictEqual(DeeplinkSchemaMatch.hasNeededJsonKeysForMultiSigSharing(isNotAllowed2), false);
});
});