I'm building a React Native app that is using redux-persist and redux-persist-transform-encrypt to store app state encrypted on the device. redux-persist-transform-encrypt uses CryptoJs to encrypt data via AES: CryptoJS.AES.encrypt('serialized app state', 'some-secret-key').
My question: (I'm a crypto novice) is there a best practice for generating some-secret-key? My plan is to generate this key randomly when the app boots for the first time and store it securely in the device keychain.
We use React Native Keychain to store the random generate key, to ensure the key is harder to get even if the attacker have access to file system:
let secretKey
async function getSecretKey() {
let {password} = await Keychain.getGenericPassword()
if (!password) {
password = generateRandomKey()
await Keychain.setGenericPassword(appName, password)
}
return password
}
const encrypt = async function(state) {
if (!secretKey) {
secretKey = await getSecretKey()
}
if (typeof state !== 'string') {
state = stringify(state);
}
return CryptoJS.AES.encrypt(state, secretKey).toString()
}
You can use some uuid generator to generate random secret key. then store it in the keychain as #jimchao said. If you are using expo you can write something as follows:
import Expo from 'expo';
export const getFromSecureStore = (key, options) =>
Expo.SecureStore.getItemAsync(key, options);
export const saveToSecureStore = (key, value, options) =>
Expo.SecureStore.setItemAsync(key, value, options);
then use it as:
// while signing in
function* handleSignInRequest(action) {
try {
const resp = yield RequestService.post(
action.payload.url,
action.payload.body,
);
yield put({ type: SIGN_IN_SUCCEEDED, payload: resp.data });
const { username, password } = action.payload.body;
yield saveToSecureStore('username', username, {
keychainAccessible: Expo.SecureStore.ALWAYS, // can be changed as per usage
});
yield saveToSecureStore('password', password, {
keychainAccessible: Expo.SecureStore.ALWAYS,
});
} catch (error) {
console.log('======>> signin error', error);
yield put({ type: SIGN_IN_FAILED, error: error.response });
}
}
// at app start
async handleSignInAtBootstrap() {
try {
const username = await getFromSecureStore('username');
const password = await getFromSecureStore('password');
this.props.signInUser(username, password); //dispatch an action
} catch (error) {
console.log('======>>>', error);
}
}
here's Expo secure store docs to explore various configs
Related
I have minted some NFTs on opensea. These are on Polygon Mumbai network. Now I want to transfer these to token to other addresses using alchemy web3. Here is the code I am using.
Note: This is supposed to run in nodejs restful API, so there is no wallet available that why I am manually signing the transaction.
async function main() {
require('dotenv').config();
const { API_URL,API_URL_TEST, PRIVATE_KEY } = process.env;
const { createAlchemyWeb3 } = require("#alch/alchemy-web3");
const web3 = createAlchemyWeb3(API_URL_TEST);
const myAddress = '*************************'
const nonce = await web3.eth.getTransactionCount(myAddress, 'latest');
const transaction = { //I believe transaction object is not correct, and I dont know what to put here
'asset': {
'tokenId': '******************************',//NFT token id in opensea
},
'gas': 53000,
'to': '***********************', //metamask address of the user which I want to send the NFT
'quantity': 1,
'nonce': nonce,
}
const signedTx = await web3.eth.accounts.signTransaction(transaction, PRIVATE_KEY);
web3.eth.sendSignedTransaction(signedTx.rawTransaction, function(error, hash) {
if (!error) {
console.log("🎉 The hash of your transaction is: ", hash, "\n Check Alchemy's Mempool to view the status of your transaction!");
} else {
console.log("âť—Something went wrong while submitting your transaction:", error)
}
});
}
main();
Assumed that you have Metamask installed in your browser, and that the NFT smart contract follows ERC721 Standard
const { API_URL,API_URL_TEST, PRIVATE_KEY } = process.env;
const { createAlchemyWeb3 } = require("#alch/alchemy-web3");
const {abi} = YOUR_CONTRACT_ABI
const contract_address = CONTRACT ADDRESS
require('dotenv').config();
async function main() {
const web3 = createAlchemyWeb3(API_URL_TEST);
web3.eth.getAccounts().then(accounts => {
const account = account[0]
const nameContract = web3.eth.Contract(abi, contract_address);
nameContract.methods.transfer(account, ADDRESS_OF_WALLET_YOU_WANT_TO_SEND_TO, TOKEN_ID).send();
})
.catch(e => console.log(e));
}
main();
Had the same problem, because there is no example on transferring NFT token.
There is a well explained 3-parts-example on ethereum website to mint an NFT.
In the first part, step 10, it explains how to write a contract and also mentions the existing methods in the contract object extended:
After our import statements, we have our custom NFT smart contract, which is surprisingly short — it only contains a counter, a constructor, and single function! This is thanks to our inherited OpenZeppelin contracts, which implement most of the methods we need to create an NFT, such as ownerOf which returns the owner of the NFT, and transferFrom, which transfers ownership of the NFT from one account to another.
So, with these informations, I made an NFT transfer transaction between two addresses with my metamask mobile app. Then I searched the JSON of this transaction through etherscan API.
In this way, I was able to transfer tokens to other addresses using alchemy web3 with this script:
require("dotenv").config()
const API_URL = process.env.API_URL; //the alchemy app url
const PUBLIC_KEY = process.env.PUBLIC_KEY; //my metamask public key
const PRIVATE_KEY = process.env.PRIVATE_KEY;//my metamask private key
const {createAlchemyWeb3} = require("#alch/alchemy-web3")
const web3 = createAlchemyWeb3(API_URL)
const contract = require("../artifacts/contracts/MyNFT.sol/MyNFT.json")//this is the contract created from ethereum example site
const contractAddress = "" // put here the contract address
const nftContract = new web3.eth.Contract(contract.abi, contractAddress)
/**
*
* #param tokenID the token id we want to exchange
* #param to the metamask address will own the NFT
* #returns {Promise<void>}
*/
async function exchange(tokenID, to) {
const nonce = await web3.eth.getTransactionCount(PUBLIC_KEY, 'latest');
//the transaction
const tx = {
'from': PUBLIC_KEY,
'to': contractAddress,
'nonce': nonce,
'gas': 500000,
'input': nftContract.methods.safeTransferFrom(PUBLIC_KEY, to, tokenID).encodeABI() //I could use also transferFrom
};
const signPromise = web3.eth.accounts.signTransaction(tx, PRIVATE_KEY)
signPromise
.then((signedTx) => {
web3.eth.sendSignedTransaction(
signedTx.rawTransaction,
function (err, hash) {
if (!err) {
console.log(
"The hash of your transaction is: ",
hash,
"\nCheck Alchemy's Mempool to view the status of your transaction!"
)
} else {
console.log(
"Something went wrong when submitting your transaction:",
err
)
}
}
)
})
.catch((err) => {
console.log(" Promise failed:", err)
})
}
I Had the same problem. I need to transfer NFT in node.js back-end.
I use network provider to use Moralis NetworkWeb3Connector.
here's my repository for example:
https://github.com/HanJaeJoon/Web3API/blob/2e30e89e38b7b1f947f4977a0fe613c882099fbc/views/index.ejs#L259-L275
await Moralis.start({
serverUrl,
appId,
masterKey,
});
await Moralis.enableWeb3({
// rinkeby
chainId: 0x4,
privateKey: process.env.PRIVATE_KEY,
provider: 'network',
speedyNodeApiKey: process.env.MORALIS_SPEEDY_NODE_API_KEY,
});
const options = {
type,
receiver,
contractAddress,
tokenId,
amount: 1,
};
try {
await Moralis.transfer(options);
} catch (error) {
console.log(error);
}
you can get speed node api key in
Moralis dashboad > Networks > Eth Rinkeby(in my case) > Settings
screenshot
I'm trying to make a request to the KuCoin API to query the balance. I'm using the NodeJS API found here but I keep getting the error whenever I execute the code.
And here's the code snippet
data().then(api => {
const apiKey = api.api_key;
const apiSecretKey = api.api_secret;
const contactId = api.contact_id;
const exchange = api.exchange;
const passphrase = 'Passphrase';
/** Init Configure */
const config =
{
key: apiKey, // KC-API-KEY
secret: apiSecretKey, // API-Secret
passphrase: passphrase, // KC-API-PASSPHRASE
environment: "live"
}
API.init(require(config));
if (apiKey && exchange === "KuCoin-Futures") {
console.log("KuCoin Balance")
async function getBalance() {
try {
let r = await API.getAccountOverview()
console.log(r.data)
} catch(err) {
console.log(err)
}
}
return getBalance()
}
});
I the console log I get the following error
TypeError: request.charAt is not a function
at Function.Module._resolveLookupPaths (internal/modules/cjs/loader.js:617:15)
Does anyone know how I can fix this??
There are couple of things which look weird in the code snippet you provided, but the sample code from the kucoin-node-api library you linked should work perfectly fine. In case you are using that one, try this snippet which should show your account info:
const api = require('kucoin-node-api');
const config = {
apiKey: 'YOUR_KUCOIN_API_KEY',
secretKey: 'YOUR_KUCOIN_API_SECRET',
passphrase: 'YOUR_KUCOIN_API_PASSPHRASE',
environment: 'live'
};
api.init(config);
api.getAccounts().then((r) => {
console.log(r.data);
}).catch((e) => {
console.log(e);
});
In case you're using a different library, kucoin-node-sdk maybe (judging by your code snippet), then try to configure it correctly:
config.js file:
module.exports = {
baseUrl: 'https://api.kucoin.com',
apiAuth: {
key: 'YOUR_KUCOIN_API_KEY',
secret: 'YOUR_KUCOIN_API_SECRET',
passphrase: 'YOUR_KUCOIN_API_PASSPHRASE'
},
authVersion: 2
}
and your main.js (or whatever the name is):
const API = require('kucoin-node-sdk');
API.init(require('./config'));
const main = async () => {
const getTimestampRl = await API.rest.Others.getTimestamp();
console.log(getTimestampRl.data);
};
main();
The code above will show you KuCoin server timestamp only, but should be enough to keep going.
Good luck with trading!
I am using #aws-crypto/client-node npm module to encrypt decrypt file using KMS key.
but when I run the following code.
I get error 'Missing credentials in config'
const {
KmsKeyringNode,
encrypt,
decrypt
} = require("#aws-crypto/client-node");
const encryptData = async (plainText, context) => {
try {
const {
result
} = await encrypt(keyring, plainText, {
encryptionContext: context
});
return result;
} catch (e) {
console.log(e);
}
};
encryptData('hello world', {
stage: "test",
purpose: "poc",
origin: "us-east-1"
})
I can see couple of issues with this code:
You are trying to import encrypt and decrypt functions directly from the module. This is not how aws-crypto works. You need to use build client to create instance which will hold these methods.
You are using keyring variable, but keyring is never declared? You need to create a keyring using .KmsKeyringNode method.
In order to properly use AWS/KMS to encrypt and decrypt data, take a look into the example bellow. (Make a note that this example does not use a context for its simplicity, nor additional keys which you can add. Also this example assumes that you are using same key for encryption and decryption as well)
const {
AMAZON_ENCRYPTION_KEY_ARN
} = process.env;
const awsCrypto = require('#aws-crypto/client-node');
const awsEncryptionClient = awsCrypto.buildClient(
awsCrypto.CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT
);
const keyring = new awsCrypto.KmsKeyringNode({
generatorKeyId: AMAZON_ENCRYPTION_KEY_ARN
});
const encrypt = async (data) => {
try {
const { result } = await awsEncryptionClient.encrypt(keyring, data);
return result.toString('base64');
}
catch(err) {
console.log('Encryption error: ', err);
throw err;
}
};
const decrypt = async (encryptedData) => {
try {
const encryptedBuffer = Buffer.from(encryptedData, 'base64');
const { plaintext } = await awsEncryptionClient.decrypt(keyring, encryptedBuffer);
return plaintext.toString('utf8');
}
catch(err) {
console.log('Decryption error: ', err);
throw err;
}
};
module.exports = {
encrypt,
decrypt
};
You can create a file using code above and invoke functions by importing this file somewhere else. You will need to add encryption/decryption key arn. Beside encryption and decryption, encoding and decoding to base64 is added, so final result is suitable for storage (database for example)
For additional code examples take a look here.
I am new to the firebase (and all its features) space. I have read the documentation, and I have been able to use the web sdk properly. I have created a file where all my current firebase code is written as seen in firebaseApi.js below. Also, below is an example of how I have used the functions under registration.js (Kindly correct if I am doing it wrong), the sample works. I was trying to implement
admin.auth().getUserByPhoneNumber(phoneNumber),
which I want to use to check if a currently inputted phone number already exists in the App. But I have read the Admin SDKs cannot be used in client-side environments and should only be used in privileged server environments owned or managed by the developers of a Firebase app. I am kinda lost on how to go around this.
is it possible to connect firebase cloud functions to the client-side like
I am doing with the firebaseApi?
I have cleaned up the code and kept only the relevant parts
firebaseApi.js
import firebase from 'firebase/app';
import 'firebase/firestore';
import 'firebase/auth';
import 'firebase/database';
import 'firebase/storage';
const config = {config};
firebase.initializeApp(config);
class Firebase {
register = ({ fullname, email, phone }) => {
const user = Firebase.auth.currentUser.uid;
const firestoreRef = Firebase.firestore.collection('Users').doc(user);
const settings = {
fullname,
email,
phone,
};
firestoreRef
.set(settings);
};
static init() {
Firebase.auth = firebase.auth();
Firebase.firestore = firebase.firestore();
Firebase.database = firebase.database();
Firebase.storage = firebase.storage();
Firebase.email = firebase.auth.EmailAuthProvider;
Firebase.google = firebase.auth.GoogleAuthProvider;
Firebase.phoneVerify = new firebase.auth.PhoneAuthProvider();
Firebase.phone = firebase.auth.PhoneAuthProvider;
}
}
Firebase.shared = new Firebase();
export default Firebase;
registration.js
import Firebase from './firebaseApi';
onCompleteReg() {
const { fullname, email, email } = this.state;
const settings = {
fullname,
email,
email
};
Firebase.shared
.registerSettings(settings)
.then(() => {
console.log('Successful');
}).catch((e) => {
console.log(e);
})
}
As a matter of privacy and best practices, unless the current user is an administrator, I would not be exposing the ability to check if any given phone number is used by any individual and/or is tied to your application.
Wrapped in Cloud Function
As the Admin SDK is to be used only from secure environments, you can only expose it's functionality by way of some API. It is beneficial in this case to handle user authentication and CORS automatically, so I'll use a Callable Function. Based on the sensitive nature of such an API, it would also be advised to rate-limit access to it which can be easily achieved using the firebase-functions-rate-limiter package. In the below code, we limit the API calls to 2 uses per user and 10 uses across all users, per 15 second period to prevent abuse.
import * as admin from 'firebase-admin';
import * as functions from 'firebase-functions';
import { FirebaseFunctionsRateLimiter } from 'firebase-functions-rate-limiter';
admin.initializeApp();
const realtimeDb = admin.database();
const perUserLimiter = FirebaseFunctionsRateLimiter.withRealtimeDbBackend(
{
name: 'rate-limit-phone-check',
maxCalls: 2,
periodSeconds: 15,
},
realtimeDb
);
const globalLimiter = FirebaseFunctionsRateLimiter.withRealtimeDbBackend(
{
name: 'rate-limit-phone-check',
maxCalls: 10,
periodSeconds: 15,
},
realtimeDb
);
exports.phoneNumber = functions.https.onCall(async (data, context) => {
// assert required params
if (!data.phoneNumber) {
throw new functions.https.HttpsError(
'invalid-argument',
'Value for "phoneNumber" is required.'
);
} else if (!context.auth || !context.auth.uid) {
throw new functions.https.HttpsError(
'failed-precondition',
'The function must be called while authenticated.'
);
}
// rate limiter
const [userLimitExceeded, globalLimitExceeded] = await Promise.all(
perUserLimiter.isQuotaExceededOrRecordUsage('u_' + context.auth.uid),
globalLimiter.isQuotaExceededOrRecordUsage('global'));
if (userLimitExceeded || globalLimitExceeded) {
throw new functions.https.HttpsError(
'resource-exhausted',
'Call quota exceeded. Try again later',
);
}
let userRecord = await admin.auth.getUserByPhoneNumber(phoneNumber);
return userRecord.uid;
}
To call the check, you would use the following code on the client:
let checkPhoneNumber = firebase.functions().httpsCallable('phoneNumber');
checkPhoneNumber({phoneNumber: "61123456789"})
.then(function (result) {
let userId = result.data;
// do something with userId
})
.catch(function (error) {
console.error('Failed to check phone number: ', error)
});
Attempt by Login
Rather than allow users to find out if a phone number exists or specifically exists on your service, it is best to follow the Phone Number authentication flow and allow them to prove that they own a given phone number. As the user can't verify more than one number en-masse, this is the safest approach.
From the Firebase Phone Auth Reference, the following code is used to verify a phone number:
// 'recaptcha-container' is the ID of an element in the DOM.
var applicationVerifier = new firebase.auth.RecaptchaVerifier(
'recaptcha-container');
var provider = new firebase.auth.PhoneAuthProvider();
provider.verifyPhoneNumber('+16505550101', applicationVerifier)
.then(function(verificationId) {
var verificationCode = window.prompt('Please enter the verification ' +
'code that was sent to your mobile device.');
return firebase.auth.PhoneAuthProvider.credential(verificationId,
verificationCode);
})
.then(function(phoneCredential) {
return firebase.auth().signInWithCredential(phoneCredential);
});
Privileged Phone Search
If you want an appropriately privileged user (whether they have an administrator or management role) to be able to query users by a phone number, you can use the following scaffolding. In these code samples, I limit access to those who have the isAdmin claim on their authentication token.
Database structure: (see this answer for more info)
"phoneNumbers": {
"c011234567890": { // with CC for US
"userId1": true
},
"c611234567890": { // with CC for AU
"userId3": true
},
...
}
Database rules:
{
"rules": {
...,
"phoneNumbers": {
"$phoneNumber": {
"$userId": {
".write": "auth.uid === $userId && (!newData.exists() || root.child('users').child(auth.uid).child('phoneNumber').val() == ($phoneNumber).replace('c', ''))" // only this user can edit their own record and only if it is their phone number or they are deleting this record
}
},
".read": "auth != null && auth.token.isAdmin == true", // admins may read/write everything under /phoneNumbers
".write": "auth != null && auth.token.isAdmin == true"
}
}
}
Helper functions:
function doesPhoneNumberExist(phoneNumber) {
return firebase.database.ref("phoneNumbers").child("c" + phoneNumber).once('value')
.then((snapshot) => snapshot.exists());
}
// usage: let exists = await doesPhoneNumberExist("611234567890")
function getUsersByPhoneNumber(phoneNumber) {
return firebase.database.ref("phoneNumbers").child("c" + phoneNumber).once('value')
.then((snapshot) => snapshot.exists() ? Object.keys(snapshot.val()) : []);
}
// usage: let usersArray = await getUsersByPhoneNumber("611234567890") - normally only one user
function searchPhoneNumbersThatStartWith(str) {
if (!str || str.length < 5) return Promise.reject(new Error('Search string is too short'));
return firebase.database.ref("phoneNumbers").startAt("c" + str).endAt("c" + str + "\uf8ff").once('value')
.then((snapshot) => {
let phoneNumbers = [];
snapshot.forEach((phoneEntrySnapshot) => phoneNumbers.push(phoneEntrySnapshot.key));
return phoneNumbers;
});
}
// usage: let matches = await searchPhoneNumbersThatStartWith("61455")
// best handled by Cloud Function not client
function linkPhoneNumberWithUser(phoneNumber, userId) {
return firebase.database.ref("phoneNumbers").child("c" + phoneNumber).child(userId).set(true);
}
// usage: linkPhoneNumberWithUser("611234567890", firebase.auth().currentUser.uid)
// best handled by Cloud Function not client
function unlinkPhoneNumberWithUser(phoneNumber, userId) {
return firebase.database.ref("phoneNumbers").child("c" + phoneNumber).child(userId).remove();
}
// usage: unlinkPhoneNumberWithUser("611234567890", firebase.auth().currentUser.uid)
I want to store a secret key using Google Cloud KMS and use it in Google Cloud Function. First I will encrypt my key and store it in environment variable
If I decrypt my secret key like link, it returns Promise.
Is my variable guaranteed to be finished initializing when my function is deployed and called?
I'm the author of that code snippet and the corresponding blog post. For the post history, here's the full snippet the OP is referring to:
const cryptoKeyID = process.env.KMS_CRYPTO_KEY_ID;
const kms = require('#google-cloud/kms');
const client = new kms.v1.KeyManagementServiceClient();
let username;
client.decrypt({
name: cryptoKeyID,
ciphertext: process.env.DB_USER,
}).then(res => {
username = res[0].plaintext.toString().trim();
}).catch(err => {
console.error(err);
});
let password;
client.decrypt({
name: cryptoKeyID,
ciphertext: process.env.DB_PASS,
}).then(res => {
password = res[0].plaintext.toString().trim();
}).catch(err => {
console.error(err);
});
exports.F = (req, res) => {
res.send(`${username}:${password}`)
}
Because Node is an asynchronous language, there is no guarantee that the variables username and password are fully initialized before function invocation as-written. In that snippet, I optimized for "decrypt at function boot so each function invocation runs in constant time". In your example, you want to optimize for "the function is fully initialized before invocation" which requires some re-organization of the code.
One possible solution is to move the lookup into a Node function that is invoked when the GCF function is called. For example:
const cryptoKeyID = process.env.KMS_CRYPTO_KEY_ID;
const kms = require('#google-cloud/kms');
const client = new kms.v1.KeyManagementServiceClient();
let cache = {};
const decrypt = async (ciphertext) => {
if (!cache[ciphertext]) {
const [result] = await client.decrypt({
name: cryptoKeyID,
ciphertext: ciphertext,
});
cache[ciphertext] = result.plaintext;
}
return cache[ciphertext];
}
exports.F = async (req, res) => {
const username = await decrypt(process.env.DB_USER);
const password = await decrypt(process.env.DB_PASS);
res.send(`${username}:${password}`)
}
Note that I added a caching layer here, since you probably don't want to decrypt the encrypted blob on each invocation of the function.