Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add encryption methods #29

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 70 additions & 8 deletions src/Angor/Client/Pages/Wallet.razor
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
@inject IClipboardService _clipboardService
@inject IDerivationOperations _derivationOperations
@inject NavMenuState NavMenuState
@inject ICryptoService _cryptoService

@inherits BaseComponent

Expand Down Expand Up @@ -62,6 +63,25 @@
<div class="mb-3">
<button class="btn btn-primary" @onclick="GenerateNewWalletWords">Generate New Wallet Words</button>
</div>
<div class="mb-3">
<p>Please enter a password for your new wallet:</p>
<div class="input-group mb-3">
<input type="@passwordInputType" class="form-control" placeholder="Password" @bind="walletPassword" />
<button class="btn btn-outline-secondary" type="button" @onclick="TogglePasswordVisibility">@passwordToggleText</button>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="backupConfirmationCheckbox" @bind="backupConfirmation">
<div class="alert alert-warning" role="alert">
<label class="form-check-label" for="backupConfirmationCheckbox">
<strong>ATTENTION!!!</strong> I confirm I have backed up my wallet words and keep it private and secure. I understand that if I lose these words, I will lose access to my wallet.
</label>
</div>
</div>
@if (showBackupConfirmationError)
{
<div class="text-danger-emphasis">You must confirm that you have backed up your wallet words.</div>
}
</div>
<div class="mb-3">
<button class="btn btn-success" @onclick="CreateWalletAsync" data-bs-dismiss="modal">Create Wallet</button>
</div>
Expand Down Expand Up @@ -112,13 +132,19 @@
<button type="button" class="btn-close" data-bs-dismiss="modal" @onclick="() => { WalletWordsClose(); walletWordsModal = false; }" aria-label="Close"></button>
</div>
<div class="modal-body">

<!-- Show words button with warning -->
<div class="mb-3">
<div class="alert alert-danger" role="alert">
<p class="fs-6">Warning: These words can be used to recover your wallet. Keep them safe.</p>
</div>
</div>

@if (string.IsNullOrEmpty(walletWords))
{
<!-- Show words button with warning -->
<div class="mb-3">
<button class="btn btn-warning" @onclick="ShowWords">Show Words</button>
<p class="fs-6 text-danger">Warning: These words can be used to recover your wallet. Keep them safe.</p>
<button class="btn btn-warning mb-3" @onclick="ShowWords">Show Words</button>
</div>
}
else
Expand All @@ -133,14 +159,13 @@

<!-- Copy words to clipboard -->
<div class="mb-3">
<button class="btn btn-danger" @onclick="CopyWordsToClibboard">Copy To clipboard</button>
<p class="fs-6 text-danger">Warning: These words can be used to recover your wallet. Keep them safe.</p>
<button class="btn btn-danger mb-3" @onclick="CopyWordsToClibboard">Copy To clipboard</button>
</div>
}

<hr>
<!-- Delete wallet button -->
<div class="mb-3">
<button class="btn btn-danger" @onclick="DeleteWallet" data-bs-dismiss="modal">Delete Wallet</button>
<button class="btn btn-danger" @onclick="DeleteWallet" data-bs-dismiss="modal">Danger!!! Delete Wallet</button>
</div>

</div>
Expand Down Expand Up @@ -379,6 +404,12 @@
private bool walletWordsModal;
private bool walletWordsCreateModal;

private string walletPassword = string.Empty;
private string passwordInputType = "password";
private string passwordToggleText = "Show";
private bool showBackupConfirmationError = false;
private bool backupConfirmation = false;

private AccountInfo localAccountInfo = new();

private int feeRange = 0;
Expand Down Expand Up @@ -436,13 +467,24 @@

private async Task CreateWalletAsync()
{
if (string.IsNullOrEmpty(newWalletWords)) throw new ArgumentNullException();
if (string.IsNullOrEmpty(newWalletWords)) throw new ArgumentNullException(nameof(newWalletWords));

if (string.IsNullOrEmpty(walletPassword)) throw new ArgumentNullException(nameof(walletPassword));

if (!backupConfirmation)
{
showBackupConfirmationError = true;
return;
}

var operationResult = await notificationComponent.LongOperation(async () =>
{
walletWordsCreateModal = false;

WalletWords data = new WalletWords { Words = newWalletWords, Passphrase = newWalletWordsPassphrase };

var encrypted = await _cryptoService.EncryptDataAsync(walletPassword, data.ConvertToString());

var accountInfo = _walletOperations.BuildAccountInfoForWalletWords(data);
await _walletOperations.UpdateAccountInfoWithNewAddressesAsync(accountInfo);
Network network = _networkConfiguration.GetNetwork();
Expand All @@ -467,9 +509,23 @@
}
}

private void TogglePasswordVisibility()
{
if (passwordInputType == "password")
{
passwordInputType = "text";
passwordToggleText = "Hide";
}
else
{
passwordInputType = "password";
passwordToggleText = "Show";
}
}

private void ShowWords()
{
var data = _walletStorage.GetWallet();
var data = _walletStorage.GetWallet();
walletWords = data.Words;
walletWordsPassphrase = data.Passphrase;
StateHasChanged();
Expand All @@ -493,11 +549,17 @@
walletWordsPassphrase = null;
newWalletWords = null;
newWalletWordsPassphrase = null;
walletPassword = string.Empty;
backupConfirmation = false;
showBackupConfirmationError = false;
}

private void WalletWordsClose()
{
walletWords = null;
walletPassword = string.Empty;
backupConfirmation = false;
showBackupConfirmationError = false;
}

private void DeleteWallet()
Expand Down
1 change: 1 addition & 0 deletions src/Angor/Client/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
builder.Services.AddScoped<ICacheStorage, LocalSessionStorage>();
builder.Services.AddTransient<IWalletOperations, WalletOperations>();
builder.Services.AddScoped<IClipboardService, ClipboardService>();
builder.Services.AddScoped<ICryptoService, CryptoService>();
builder.Services.AddScoped<IDerivationOperations, DerivationOperations>();
builder.Services.AddScoped<NavMenuState>();

Expand Down
25 changes: 25 additions & 0 deletions src/Angor/Client/Services/CryptoService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Microsoft.JSInterop;

namespace Angor.Client.Services
{
public class CryptoService : ICryptoService
{
private readonly IJSRuntime _jsRuntime;

public CryptoService(IJSRuntime jsRuntime)
{
_jsRuntime = jsRuntime;
}

public async Task<string> EncryptDataAsync(string password, string secretData)
{
return await _jsRuntime.InvokeAsync<string>("encryptData", secretData, password);
}

public async Task<string> DecryptDataAsync(string password, string encryptedData)
{
return await _jsRuntime.InvokeAsync<string>("decryptData", encryptedData, password);
}
}

}
8 changes: 8 additions & 0 deletions src/Angor/Client/Services/ICryptoService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Angor.Client.Services;

public interface ICryptoService
{
Task<string> EncryptDataAsync(string password, string secretData);

Task<string> DecryptDataAsync(string password, string encryptedData);
}
80 changes: 80 additions & 0 deletions src/Angor/Client/wwwroot/crypto-tools.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Import the Base64 module from 'js-base64'
import { Base64 } from 'js-base64';

async function encryptData(secretData, password) {
try {
const enc = new TextEncoder();
const salt = window.crypto.getRandomValues(new Uint8Array(16));
const iv = window.crypto.getRandomValues(new Uint8Array(12));
const passwordKey = await getPasswordKey(password);
const aesKey = await deriveKey(passwordKey, salt, ["encrypt"]);
const encryptedContent = await window.crypto.subtle.encrypt(
{
name: "AES-GCM",
iv: iv,
},
aesKey,
enc.encode(secretData)
);

const encryptedContentArr = new Uint8Array(encryptedContent);
let buff = new Uint8Array(salt.byteLength + iv.byteLength + encryptedContentArr.byteLength);
buff.set(salt, 0);
buff.set(iv, salt.byteLength);
buff.set(encryptedContentArr, salt.byteLength + iv.byteLength);

return Base64.fromUint8Array(buff); // Ensure you have a Base64.fromUint8Array implementation
} catch (e) {
console.error(e);
return "";
}
}

async function decryptData(encryptedData, password) {
try {
const dec = new TextDecoder();
const encryptedDataBuff = Base64.toUint8Array(encryptedData); // Ensure you have a Base64.toUint8Array implementation

const salt = encryptedDataBuff.slice(0, 16);
const iv = encryptedDataBuff.slice(16, 16 + 12);
const data = encryptedDataBuff.slice(16 + 12);
const passwordKey = await getPasswordKey(password);
const aesKey = await deriveKey(passwordKey, salt, ["decrypt"]);
const decryptedContent = await window.crypto.subtle.decrypt(
{
name: "AES-GCM",
iv: iv,
},
aesKey,
data
);
return dec.decode(decryptedContent);
} catch (e) {
console.error(e);
return "";
}
}

const enc = new TextEncoder();
const dec = new TextDecoder();

function getPasswordKey(password) {
return window.crypto.subtle.importKey("raw", enc.encode(password), "PBKDF2", false, ["deriveKey"]);
}

function deriveKey(passwordKey, salt, keyUsage) {
return window.crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt: salt,
iterations: 250000,
hash: "SHA-256",
},
passwordKey,
{ name: "AES-GCM", length: 256 },
false,
keyUsage
);
}

window.encryptData = encryptData;
48 changes: 24 additions & 24 deletions src/Angor/Client/wwwroot/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,32 +19,32 @@

</head>

<body>
<div id="app">

<div class="loader-wrapper">
<!--<div class="loading-text">Loading Angor...</div>--> <!-- Text above the loader -->
<div class="loader"></div>
</div>

<!--<svg class="loading-progress">
<circle r="40%" cx="50%" cy="50%" />
<circle r="40%" cx="50%" cy="50%" />
</svg>
<div class="loading-progress-text"></div>-->
</div>
<body>
<div id="app">

<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
<div class="loader-wrapper">
<!--<div class="loading-text">Loading Angor...</div>--> <!-- Text above the loader -->
<div class="loader"></div>
</div>

<script src="css/bootstrap/bootstrap.bundle.min.js"></script>

<script src="_framework/blazor.webassembly.js"></script>
<script>navigator.serviceWorker.register('service-worker.js');</script>

</body>
<!--<svg class="loading-progress">
<circle r="40%" cx="50%" cy="50%" />
<circle r="40%" cx="50%" cy="50%" />
</svg>
<div class="loading-progress-text"></div>-->
</div>

<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>

<script src="css/bootstrap/bootstrap.bundle.min.js"></script>
<script src="_framework/blazor.webassembly.js"></script>
<script src="crypto-tools.js"></script>>
<script>navigator.serviceWorker.register('service-worker.js');</script>

</body>

</html>
Loading