Skip to content

Commit

Permalink
feat(rust,rust-nodejs,ironfish): Remove old encrypt/decrypt methods (#…
Browse files Browse the repository at this point in the history
…5377)

* feat(ironfish): Create master key

* feat(rust,rust-nodejs,ironfish): Remove old encrypt/decrypt methods

* chore(rust): lint

* test(ironfish): Fix tests

* Fix test

* fixtures
  • Loading branch information
rohanjadvani authored Sep 17, 2024
1 parent e3a51ce commit 1249c3c
Show file tree
Hide file tree
Showing 22 changed files with 265 additions and 402 deletions.
2 changes: 0 additions & 2 deletions ironfish-rust-nodejs/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,6 @@ export const TRANSACTION_EXPIRATION_LENGTH: number
export const TRANSACTION_FEE_LENGTH: number
export const LATEST_TRANSACTION_VERSION: number
export declare function verifyTransactions(serializedTransactions: Array<Buffer>): boolean
export declare function encrypt(plaintext: Buffer, passphrase: string): Buffer
export declare function decrypt(encryptedBlob: Buffer, passphrase: string): Buffer
export const enum LanguageCode {
English = 0,
ChineseSimplified = 1,
Expand Down
4 changes: 1 addition & 3 deletions ironfish-rust-nodejs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ if (!nativeBinding) {
throw new Error(`Failed to load native binding`)
}

const { FishHashContext, deserializePublicPackage, deserializeRound2CombinedPublicPackage, KEY_LENGTH, NONCE_LENGTH, BoxKeyPair, randomBytes, boxMessage, unboxMessage, RollingFilter, initSignalHandler, triggerSegfault, ASSET_ID_LENGTH, ASSET_METADATA_LENGTH, ASSET_NAME_LENGTH, ASSET_LENGTH, Asset, NOTE_ENCRYPTION_KEY_LENGTH, MAC_LENGTH, ENCRYPTED_NOTE_PLAINTEXT_LENGTH, ENCRYPTED_NOTE_LENGTH, NoteEncrypted, PUBLIC_ADDRESS_LENGTH, RANDOMNESS_LENGTH, MEMO_LENGTH, AMOUNT_VALUE_LENGTH, DECRYPTED_NOTE_LENGTH, Note, PROOF_LENGTH, TRANSACTION_SIGNATURE_LENGTH, TRANSACTION_PUBLIC_KEY_RANDOMNESS_LENGTH, TRANSACTION_EXPIRATION_LENGTH, TRANSACTION_FEE_LENGTH, LATEST_TRANSACTION_VERSION, TransactionPosted, Transaction, verifyTransactions, UnsignedTransaction, encrypt, decrypt, LanguageCode, generateKey, spendingKeyToWords, wordsToSpendingKey, generatePublicAddressFromIncomingViewKey, generateKeyFromPrivateKey, initializeSapling, FoundBlockResult, ThreadPoolHandler, isValidPublicAddress, CpuCount, getCpuCount, generateRandomizedPublicKey, multisig, xchacha20poly1305 } = nativeBinding
const { FishHashContext, deserializePublicPackage, deserializeRound2CombinedPublicPackage, KEY_LENGTH, NONCE_LENGTH, BoxKeyPair, randomBytes, boxMessage, unboxMessage, RollingFilter, initSignalHandler, triggerSegfault, ASSET_ID_LENGTH, ASSET_METADATA_LENGTH, ASSET_NAME_LENGTH, ASSET_LENGTH, Asset, NOTE_ENCRYPTION_KEY_LENGTH, MAC_LENGTH, ENCRYPTED_NOTE_PLAINTEXT_LENGTH, ENCRYPTED_NOTE_LENGTH, NoteEncrypted, PUBLIC_ADDRESS_LENGTH, RANDOMNESS_LENGTH, MEMO_LENGTH, AMOUNT_VALUE_LENGTH, DECRYPTED_NOTE_LENGTH, Note, PROOF_LENGTH, TRANSACTION_SIGNATURE_LENGTH, TRANSACTION_PUBLIC_KEY_RANDOMNESS_LENGTH, TRANSACTION_EXPIRATION_LENGTH, TRANSACTION_FEE_LENGTH, LATEST_TRANSACTION_VERSION, TransactionPosted, Transaction, verifyTransactions, UnsignedTransaction, LanguageCode, generateKey, spendingKeyToWords, wordsToSpendingKey, generatePublicAddressFromIncomingViewKey, generateKeyFromPrivateKey, initializeSapling, FoundBlockResult, ThreadPoolHandler, isValidPublicAddress, CpuCount, getCpuCount, generateRandomizedPublicKey, multisig, xchacha20poly1305 } = nativeBinding

module.exports.FishHashContext = FishHashContext
module.exports.deserializePublicPackage = deserializePublicPackage
Expand Down Expand Up @@ -292,8 +292,6 @@ module.exports.TransactionPosted = TransactionPosted
module.exports.Transaction = Transaction
module.exports.verifyTransactions = verifyTransactions
module.exports.UnsignedTransaction = UnsignedTransaction
module.exports.encrypt = encrypt
module.exports.decrypt = decrypt
module.exports.LanguageCode = LanguageCode
module.exports.generateKey = generateKey
module.exports.spendingKeyToWords = spendingKeyToWords
Expand Down
25 changes: 1 addition & 24 deletions ironfish-rust-nodejs/src/xchacha20poly1305.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use ironfish::xchacha20poly1305::{
self, EncryptOutput, XChaCha20Poly1305Key, KEY_LENGTH as KEY_SIZE, SALT_LENGTH as SALT_SIZE,
XChaCha20Poly1305Key, KEY_LENGTH as KEY_SIZE, SALT_LENGTH as SALT_SIZE,
XNONCE_LENGTH as XNONCE_SIZE,
};
use napi::{bindgen_prelude::*, JsBuffer};
Expand Down Expand Up @@ -139,26 +139,3 @@ impl NativeXChaCha20Poly1305Key {
Ok(Buffer::from(&result[..]))
}
}

#[napi]
pub fn encrypt(plaintext: JsBuffer, passphrase: String) -> Result<Buffer> {
let plaintext_bytes = plaintext.into_value()?;
let result = xchacha20poly1305::encrypt(plaintext_bytes.as_ref(), passphrase.as_bytes())
.map_err(to_napi_err)?;

let mut vec: Vec<u8> = vec![];
result.write(&mut vec).map_err(to_napi_err)?;

Ok(Buffer::from(&vec[..]))
}

#[napi]
pub fn decrypt(encrypted_blob: JsBuffer, passphrase: String) -> Result<Buffer> {
let encrypted_bytes = encrypted_blob.into_value()?;

let encrypted_output = EncryptOutput::read(encrypted_bytes.as_ref()).map_err(to_napi_err)?;
let result =
xchacha20poly1305::decrypt(encrypted_output, passphrase.as_bytes()).map_err(to_napi_err)?;

Ok(Buffer::from(&result[..]))
}
105 changes: 1 addition & 104 deletions ironfish-rust/src/xchacha20poly1305.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

use std::io;

use argon2::Argon2;
use argon2::RECOMMENDED_SALT_LEN;
use argon2::{password_hash::SaltString, Argon2};
use chacha20poly1305::aead::Aead;
use chacha20poly1305::{Key, KeyInit, XChaCha20Poly1305, XNonce};
use hkdf::Hkdf;
Expand Down Expand Up @@ -133,109 +133,6 @@ impl XChaCha20Poly1305Key {
}
}

#[derive(Debug)]
pub struct EncryptOutput {
pub salt: Vec<u8>,

pub nonce: [u8; XNONCE_LENGTH],

pub ciphertext: Vec<u8>,
}

impl EncryptOutput {
pub fn write<W: io::Write>(&self, mut writer: W) -> Result<(), IronfishError> {
let salt_len = u32::try_from(self.salt.len())?.to_le_bytes();
writer.write_all(&salt_len)?;
writer.write_all(&self.salt)?;

writer.write_all(&self.nonce)?;

let ciphertext_len = u32::try_from(self.ciphertext.len())?.to_le_bytes();
writer.write_all(&ciphertext_len)?;
writer.write_all(&self.ciphertext)?;

Ok(())
}

pub fn read<R: io::Read>(mut reader: R) -> Result<Self, IronfishError> {
let mut salt_len = [0u8; 4];
reader.read_exact(&mut salt_len)?;
let salt_len = u32::from_le_bytes(salt_len) as usize;

let mut salt = vec![0u8; salt_len];
reader.read_exact(&mut salt)?;

let mut nonce = [0u8; XNONCE_LENGTH];
reader.read_exact(&mut nonce)?;

let mut ciphertext_len = [0u8; 4];
reader.read_exact(&mut ciphertext_len)?;
let ciphertext_len = u32::from_le_bytes(ciphertext_len) as usize;

let mut ciphertext = vec![0u8; ciphertext_len];
reader.read_exact(&mut ciphertext)?;

Ok(EncryptOutput {
salt,
nonce,
ciphertext,
})
}
}

impl PartialEq for EncryptOutput {
fn eq(&self, other: &EncryptOutput) -> bool {
self.salt == other.salt && self.nonce == other.nonce && self.ciphertext == other.ciphertext
}
}

fn derive_key(passphrase: &[u8], salt: &[u8]) -> Result<Key, IronfishError> {
let mut key = [0u8; KEY_LENGTH];
let argon2 = Argon2::default();

argon2
.hash_password_into(passphrase, salt, &mut key)
.map_err(|_| IronfishError::new(IronfishErrorKind::FailedArgon2Hash))?;

Ok(Key::from(key))
}

pub fn encrypt(plaintext: &[u8], passphrase: &[u8]) -> Result<EncryptOutput, IronfishError> {
let salt = SaltString::generate(&mut thread_rng());
let salt_str = salt.to_string();
let salt_bytes = salt_str.as_bytes();
let key = derive_key(passphrase, salt_bytes)?;

let cipher = XChaCha20Poly1305::new(&key);
let mut nonce_bytes = [0u8; XNONCE_LENGTH];
thread_rng().fill_bytes(&mut nonce_bytes);
let nonce = XNonce::from_slice(&nonce_bytes);

let ciphertext = cipher
.encrypt(nonce, plaintext)
.map_err(|_| IronfishError::new(IronfishErrorKind::FailedXChaCha20Poly1305Encryption))?;

Ok(EncryptOutput {
salt: salt_bytes.to_vec(),
nonce: nonce_bytes,
ciphertext,
})
}

pub fn decrypt(
encrypted_output: EncryptOutput,
passphrase: &[u8],
) -> Result<Vec<u8>, IronfishError> {
let nonce = XNonce::from_slice(&encrypted_output.nonce);

let key = derive_key(passphrase, &encrypted_output.salt[..])?;
let cipher = XChaCha20Poly1305::new(&key);

cipher
.decrypt(nonce, encrypted_output.ciphertext.as_ref())
.map_err(|_| IronfishError::new(IronfishErrorKind::FailedXChaCha20Poly1305Decryption))
}

#[cfg(test)]
mod test {
use crate::xchacha20poly1305::XChaCha20Poly1305Key;
Expand Down
4 changes: 1 addition & 3 deletions ironfish/src/rpc/routes/wallet/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ routes.register<typeof CreateAccountRequestSchema, CreateAccountResponse>(
)
}

const account = await context.wallet.createAccount(name, {
passphrase: request.data.passphrase,
})
const account = await context.wallet.createAccount(name)
if (context.wallet.nodeClient) {
void context.wallet.scan()
}
Expand Down
7 changes: 2 additions & 5 deletions ironfish/src/rpc/routes/wallet/createAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { AssertHasRpcContext } from '../rpcContext'
* Hence, we're adding a new createAccount endpoint and will eventually sunset the create endpoint.
*/

export type CreateAccountRequest = { name: string; default?: boolean; passphrase?: string }
export type CreateAccountRequest = { name: string; default?: boolean }
export type CreateAccountResponse = {
name: string
publicAddress: string
Expand All @@ -29,7 +29,6 @@ export const CreateAccountRequestSchema: yup.ObjectSchema<CreateAccountRequest>
.object({
name: yup.string().defined(),
default: yup.boolean().optional(),
passphrase: yup.string().optional(),
})
.defined()

Expand All @@ -49,9 +48,7 @@ routes.register<typeof CreateAccountRequestSchema, CreateAccountResponse>(

let account
try {
account = await context.wallet.createAccount(request.data.name, {
passphrase: request.data.passphrase,
})
account = await context.wallet.createAccount(request.data.name)
} catch (e) {
if (e instanceof DuplicateAccountNameError) {
throw new RpcValidationError(e.message, 400, RPC_ERROR_CODES.DUPLICATE_ACCOUNT_NAME)
Expand Down
2 changes: 1 addition & 1 deletion ironfish/src/rpc/routes/wallet/rename.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ routes.register<typeof RenameAccountRequestSchema, RenameAccountResponse>(
AssertHasRpcContext(request, context, 'wallet')

const account = getAccount(context.wallet, request.data.account)
await account.setName(request.data.newName, { passphrase: request.data.passphrase })
await context.wallet.setName(account, request.data.newName)
request.end()
},
)
5 changes: 2 additions & 3 deletions ironfish/src/rpc/routes/wallet/renameAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,13 @@ import { routes } from '../router'
import { AssertHasRpcContext } from '../rpcContext'
import { getAccount } from './utils'

export type RenameAccountRequest = { account: string; newName: string; passphrase?: string }
export type RenameAccountRequest = { account: string; newName: string }
export type RenameAccountResponse = undefined

export const RenameAccountRequestSchema: yup.ObjectSchema<RenameAccountRequest> = yup
.object({
account: yup.string().defined(),
newName: yup.string().defined(),
passphrase: yup.string().optional(),
})
.defined()

Expand All @@ -29,7 +28,7 @@ routes.register<typeof RenameAccountRequestSchema, RenameAccountResponse>(
AssertHasRpcContext(request, context, 'wallet')

const account = getAccount(context.wallet, request.data.account)
await account.setName(request.data.newName, { passphrase: request.data.passphrase })
await context.wallet.setName(account, request.data.newName)
request.end()
},
)
3 changes: 0 additions & 3 deletions ironfish/src/rpc/routes/wallet/resetAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ export type ResetAccountRequest = {
account: string
resetCreatedAt?: boolean
resetScanningEnabled?: boolean
passphrase?: string
}
export type ResetAccountResponse = undefined

Expand All @@ -20,7 +19,6 @@ export const ResetAccountRequestSchema: yup.ObjectSchema<ResetAccountRequest> =
account: yup.string().defined(),
resetCreatedAt: yup.boolean(),
resetScanningEnabled: yup.boolean(),
passphrase: yup.string().optional(),
})
.defined()

Expand All @@ -39,7 +37,6 @@ routes.register<typeof ResetAccountRequestSchema, ResetAccountResponse>(
await context.wallet.resetAccount(account, {
resetCreatedAt: request.data.resetCreatedAt,
resetScanningEnabled: request.data.resetScanningEnabled,
passphrase: request.data.passphrase,
})

request.end()
Expand Down
44 changes: 22 additions & 22 deletions ironfish/src/wallet/account/__fixtures__/account.test.ts.fixture
Original file line number Diff line number Diff line change
Expand Up @@ -5909,13 +5909,13 @@
"value": {
"encrypted": false,
"version": 4,
"id": "6f0698b4-a99c-46aa-9391-59f0c55cd755",
"id": "aea047be-8c73-448e-8e8d-22844f49736f",
"name": "accountA",
"spendingKey": "89001fcdef6bff7e9fd76d4ae6275bf6786afc2797eded9df094ae4a6894782d",
"viewKey": "389bc77ae499f3edc0dc445a732add6f36c275b260efe2c791cc515f6c2c0cd75f5b31e9b1f82edb0ee57b9304ece90d48f43c36d78660b04960b9692485d058",
"incomingViewKey": "fdd70ff012b4e48576bdd71207ebe1e3747811c77fa1a25862c3b869123ce007",
"outgoingViewKey": "9664b763a0418a476d072c715fcbbba58bcaf60cc951ba017195d75453131f11",
"publicAddress": "14a9bfb247dcf632f85ff79ebef222cc9ccf364f9b3e3e0ee39b75d68f80782a",
"spendingKey": "431a4e45c614fd41d7cee2de809e4464e92a125752d6f6839c0af7c706a01f67",
"viewKey": "4be28bbdca174c4e7499d913ea02642d74136eea9058f4e6eb7586dd5c864e905128a5aa5d25356934e5f4cf36575445b8dc60bfa672e1c135521cb5610e9b4c",
"incomingViewKey": "78b6e7887d55853b84d5d0b3b80d787379750b8839ddbc738882124453a5c004",
"outgoingViewKey": "3ce3ee26d7ab6c76838b6920eb6b5cf8fa3aa1ca54940d3b3cbf20532be34e41",
"publicAddress": "09d7f58ee5ae406b19e714b7fab882b83334e5b566a64c9d9707fcc513426163",
"createdAt": {
"hash": {
"type": "Buffer",
Expand All @@ -5924,7 +5924,7 @@
"sequence": 1
},
"scanningEnabled": true,
"proofAuthorizingKey": "8c11ae2523136f4b11bada56d9bcab2b6591cc99cf7aede8a238c5fefd7fce0d"
"proofAuthorizingKey": "c95606e98895f390f247f0c9e4beb22a039adfdab5cd11dac28263870dfe4802"
},
"head": {
"hash": {
Expand All @@ -5935,18 +5935,18 @@
}
}
],
"Accounts setName should throw an error if the passphrase is incorrect and the wallet is encrypted": [
"Accounts setName should throw an error if there is no master key and the wallet is encrypted": [
{
"value": {
"encrypted": false,
"version": 4,
"id": "92b122e5-9f14-453b-a364-e20a7d107305",
"id": "df4836f9-2889-471c-b9c3-c1aaa38fc595",
"name": "accountA",
"spendingKey": "3d68ebfd3d600792fc94d583bbab97ab4d02b9f41aa2a6655e151e41d4a33d8d",
"viewKey": "51e699920432cf221351568ade21fb8af4110c7ad57ad90cc2664340d50d6f207a19eb846feb76588f467e4dce99d0da074ca680aca11d3d8c91bd47ae5d9081",
"incomingViewKey": "15f8cb20e3a7b494474f4c4737bc0403dbb5de6e532e2c83a4469cd7f95f5b02",
"outgoingViewKey": "fc1b2e0e85cca6ddaf657baf2c69c76b403afe1adadd73b794c5cb81724d240d",
"publicAddress": "899ea1e9be7202aedab0a41f6b9cf661ce20e41ba11c1ee9d15ac64d8c96a391",
"spendingKey": "433f92654fe57940d18e87481c52588fec36fa552f64dca434b1726e022722cd",
"viewKey": "1d6e8a7faea61bdfd77d6e3c3b63951b3e613a8907bd6786fe0466e3490a8558eec35c9e08b3abbbaa0b596f427801bfc306297d0c3bf1b50ad02aa62cae702f",
"incomingViewKey": "88328774271cf9b9437e0b76ffc05c5697742d2004b9430a257590cceccbb000",
"outgoingViewKey": "5cb8a083108e4f3aa7d5db9a6ddce584d9b3a941cf707ee691b016ddf6c0fc87",
"publicAddress": "534ba54bbf1721cb16e4c8a702f25d96a0c99c2f02c38431be8870a28fc04914",
"createdAt": {
"hash": {
"type": "Buffer",
Expand All @@ -5955,7 +5955,7 @@
"sequence": 1
},
"scanningEnabled": true,
"proofAuthorizingKey": "d0d08a05953a50a0ce8b35e0c410b7dde77e3bf6099aaa4fc413de2096f7ef0a"
"proofAuthorizingKey": "25079aaa8548a644fc43ba77584bb0cb53adb60e3b2a033865df05569ee44302"
},
"head": {
"hash": {
Expand All @@ -5971,13 +5971,13 @@
"value": {
"encrypted": false,
"version": 4,
"id": "1dd7e196-ffed-4fe0-8dbd-c49f82bf44b7",
"id": "de02e4f1-ad01-464d-9e5b-9b4e83c57f0c",
"name": "accountA",
"spendingKey": "71ec323628c95bd56df353ec444b8ceb3d463603e82a89d4ac3336bdf630993a",
"viewKey": "5219abc719ecb8954e77d89e60f4fc82588e8f619ba4da38b53ed0471aeccb200cc7cec29ca533c769c96213417f78ccfaf2ba3f12a4b948a0da242805eb044c",
"incomingViewKey": "d9d70e59490b44c078300a23376e4fc516b47a31231c77538d17740f399e3d00",
"outgoingViewKey": "e3eca4b31e0d4b48e27458db2eff35b4d713d84b0b07505fdc8d49b2e40ab69d",
"publicAddress": "fbc47fe75ef9534b28b95fd17288488b89a4b752191a323a3703520095e8c24b",
"spendingKey": "4c946561f929bfc55faf90cda2f3c8bc6881b0cf96f0a422acb4c2240d5b995b",
"viewKey": "3120c5db86dd17d6b11f14df5f652878306aba202c74db71aad778e45ae4888843eaa4e8fa18ee78a762c2ed3b024c29d0ccd4163b0e49dba1e9c0b2055fd5b5",
"incomingViewKey": "2b1bcb2a5821e8f0485740aaec3157d43af552e853c97fa09dedb86442095003",
"outgoingViewKey": "dff7ee410247d8d2b63cd0da40509bd8fc3e96a4d26c04ec9fdb016da2fbb2ea",
"publicAddress": "9a3f5703d2cf40fc2899bf25793249b44e187acbd5c1f6f4f20ad4143408e42e",
"createdAt": {
"hash": {
"type": "Buffer",
Expand All @@ -5986,7 +5986,7 @@
"sequence": 1
},
"scanningEnabled": true,
"proofAuthorizingKey": "f335356ed8f77c4facebc2ad9377ab05e6d71ec83aa772df0c23e2733458a10c"
"proofAuthorizingKey": "8726a9a636670be8e1fd12a0b4b42a1d0e30a526b28cae014afc7b241ad1fb0d"
},
"head": {
"hash": {
Expand Down
Loading

0 comments on commit 1249c3c

Please sign in to comment.