Mechanism for Secure Keys in Application

Problem

I am working on an application in the JS-SDK with React. The Application allows a user to take a certain action. When the user action results in a certain outcome, the application returns a reward to the user. The problem is that this requires me to include a private key in the application code for an address from which to return the reward to the user.

Screenshot 2023-06-25 at 11.45.42 AM

Potential Solutions

I am exploring several solutions for this problem. The first solution I tried was to use a secure key variable for storage. However, during penetration testing, I discovered it was possible to read the private key.

Another solution I am considering is using a .env file, however it seems this may have a similar result as the first option because ultimately, the key needs to be sent to the application. Even if the key is encrypted and not visible in the App.js file or HTML, the key would likely be visible upon deeper inspection into the network files.

A third option is to use an on-chain application in TEAL. This would then require re-writing the application almost entirely and create a host of new security problems in creating mechanisms for the front end and backend integrations and communication networks.

Comments

Ideally, there would be a solution to allow for a private key to be securely used in the application through some encryption mechanism. However, I am not sure this exists or whether it would be possible because the application ultimately needs to access the private key to function. I am also not sure how to complete a full audit of the live application to identify whether the private key is exposed.

As such, I would sincerely appreciate any thoughts or suggestions on this problem, whether the ideal solution is possible, or whether writing code for such an app securely may require an on-chain application with a new software architecture.

I created a Repo on GitHub with a minimum example of the problem. Here is the code for the App.js file.The key lines are 29 and 89, which are the key definition and the key call. I am working toward a solution that secures the private_key variable.

// Imports
import './App.css';
import algosdk from "algosdk";
import { DeflyWalletConnect } from '@blockshake/defly-connect';
import { useEffect } from 'react';


// deflywallet instantiating
const deflywallet = new DeflyWalletConnect()
// algoClient
const algod_address = "https://mainnet-api.algonode.cloud"
const algod_token = ""
const headers = {"X-Algo-API-Token": algod_token }
const algodClient = new algosdk.Algodv2(algod_token, algod_address, headers);

// get address
const address = localStorage.getItem('address');

// prize address
const prizeAddress = ''

/////////////////////////
/////////////////////////
// THIS IS THE VARIABLE THAT NEEDS PROTECTED.
// !!!!!!!!!!!!!!!!!!!!!!!!!!!
/////////////////////////
/////////////////////////
const private_key = ''
/////////////////////////
/////////////////////////
/////////////////////////

//asset id
const ASSET_ID = 297995609;

/// transaction code
const transaction = async () => {
  try{
    const suggestedParams = await algodClient.getTransactionParams().do();
    const txn = algosdk.makeAssetTransferTxnWithSuggestedParamsFromObject({
      from: address,
      to: prizeAddress,
      amount: 200,
      assetIndex: ASSET_ID,
      suggestedParams,
    });
    const optInTxn = [{txn : txn, signers: [address]}]
    const signedTxn = await deflywallet.signTransaction([optInTxn])
    const success = await algodClient.sendRawTransaction(signedTxn).do();
    return success
  }
  catch(err){
    console.log(err)
    return false
  }
  }
  
// Wallet Connect
async function walletConnect() {
  const newAccounts= await deflywallet.connect()
  localStorage.setItem("address", newAccounts[0]);
  window.location.reload()
  console.log('Connect')
  }

// wallet disconnect
const disconnect = () => {
  deflywallet.disconnect()
  localStorage.removeItem("address");
  window.location.reload()
  }

/////////////////////////
/////////////////////////
// Transaction from wallet to user upon conditional logic.
/////////////////////////
/////////////////////////
/////////////////////////
/////////////////////////
/////////////////////////

const smallestprizetransaction = async () => {
  /////////////////////////
  /////////////////////////
  // Secure variable call for private key.
  // !!!!!!!!!!!!!!!!!!!!!!!!!!!
  /////////////////////////
  const mnemonic = private_key;
  /////////////////////////
  /////////////////////////
  /////////////////////////
  /////////////////////////
  const recoveblueAccount = algosdk.mnemonicToSecretKey(mnemonic); 
  const suggestedParams = await algodClient.getTransactionParams().do();
  const txn = algosdk.makeAssetTransferTxnWithSuggestedParamsFromObject({
    from: prizeAddress,
    to: address,
    amount: 200,
    assetIndex: ASSET_ID,
    suggestedParams,
  });
  // Sign the transaction
  const signedTxn = txn.signTxn(recoveblueAccount.sk);
  const sendTx = algodClient.sendRawTransaction(signedTxn).do();
  const txId = txn.txID().toString();
  console.log("Transaction sent with ID " + sendTx.txId);
  console.log("Signed transaction with txID: %s", txId);
  // Wait for confirmation
  algosdk.waitForConfirmation(algodClient, txId, 4);
  }


/////////////////////////
/////////////////////////
// Conditional logic triggering transaction.
/////////////////////////
/////////////////////////
/////////////////////////
/////////////////////////
/////////////////////////
const guess = async () => {
  const transactionSuccess = await transaction()
  if (transactionSuccess){
    ////////////////////

    const generateNumberOne = Math.floor(Math.random() * 3)
    const generateNumberTwo = Math.floor(Math.random() * 3)
    const generateNumberThree = Math.floor(Math.random() * 3)

    document.getElementById('message1').textContent =  generateNumberOne 
    document.getElementById('message2').textContent =  generateNumberTwo 
    document.getElementById('message3').textContent =  generateNumberThree
    
    // Convert to Int
    const generateNumberOneInt = parseInt(generateNumberOne);
    const generateNumberTwoInt = parseInt(generateNumberTwo);
    const generateNumberThreeInt = parseInt(generateNumberThree);
    console.log(generateNumberOneInt)
    console.log(generateNumberTwoInt)
    console.log(generateNumberThreeInt)

    /////////////////////////
    /////////////////////////
    // Function call on conditional happening.
    /////////////////////////
    /////////////////////////
    /////////////////////////
    /////////////////////////
    /////////////////////////
    if(generateNumberOneInt===2){
      document.getElementById('message4').textContent = 'Win 2k Choice!'
      smallestprizetransaction();
    } else if(generateNumberTwoInt===2){
      document.getElementById('message4').textContent = 'Win 2k Choice'
      smallestprizetransaction();
    } else if(generateNumberThreeInt===2){
      document.getElementById('message4').textContent = 'Win 2k Choice!'
      smallestprizetransaction();
    } 
  };
}

/////////////////////////
/////////////////////////
// React App
/////////////////////////
/////////////////////////
/////////////////////////
/////////////////////////
/////////////////////////
function App() {
  useEffect(() => {
    deflywallet.reconnectSession().then((accounts) => {
      if (accounts.length) {
        localStorage.setItem("address", accounts[0]);
      }
      deflywallet.connector?.on("disconnect", () => {
        localStorage.removeItem("address");
      });
    })
    .catch((e) => console.log(e));
  }, [])
  return (
    <div className="App">
      <header className="App-header">

        <h1>
          <div id = "displaytext" style={{ color: "blue" }}> Private Key Storage Example </div>
        </h1>

        <p>
        <p>
          <button id='button1' onClick={walletConnect}> Connect</button>
          <button id='button2' onClick={disconnect}> Disconnect</button>
        </p>
        </p>


        <table>
          <tr>
            <td id='message1'></td>
            <td id='message2'></td>
            <td id='message3'></td>
          </tr>
        </table>
        <div id='message4'></div>
        <div>
          <button id='button3' onClick={guess}>Button</button>
        </div>


      </header>
    </div>
  );

}
export default App;

When you deploy it to the mainnet, please write me first… Everything that is in the browser is publicly available, so i will get your private key and get the funds :slight_smile:

I believe there is no way to make this secure from browser. Only solution is to run it in secure backend server where you would track onchain/offchain information and send the transactions to the desired accounts…

Check for example this project: GitHub - scholtz/AlgorandAMMStakingBot … With this project we do airdrops of vote coin to all vote coin holders or to the vote coin amm token holders… People can get from 10% to 50% APY of their vote coin holdings each hour. (As it is configured at the airdrop server)

1 Like

Right. The problem with doing it through a backend server is that the transaction data could then be manipulated. For example, if we write notes to the blockchain with the random numbers generated, a hacker could simply write a transaction from a wallet with the winning numbers as notes. Because the backend would simply read on-chain data it would not be secure, unless the random numbers were generated on-chain.

I looked at your example, but it’s different because you do not have user interaction on the frontend. Your project reads pool data and distributes assets accordingly.

Everything depends on the use case… i recommend you to start study pyteal, ideally with installing algokit and watching the videos from Algorand Developers - YouTube

You’re right the security is use case dependent.

I watched the intro for AlgoKit, but I’m not sure it would be of much help unless I was needed to deploy TEAL code without a node. I may check to see if there are existing random number functions in PyTeal and whether there are mechanisms for frontend communication in JS.

Buy, I think the best way to proceed may be with a backend server mechanism. But, I’ll need to invent a way to securely transmit the data from the application.

You do not need to run the node or backend if you use smart contracts. You may setup the configurable link to the algorand algod and indexer, put there default to algonode and interact directly to blockchain from the frontend.

In smart contracts you can encrypt data, you can use zero knowledge proofs, you can use random beaken… Just search this forum to find more info… there is search button in top right, and put there keyword random…

Ok. I found some good information on random numbers here. I’m still not sure what infrastructure strategy to pursue, but I appreciate your suggestions. Thanks.

I wanted to bump this up. There is a need for a method for securely storing keys for applications built with the SDKs.