From 97c247801306e09c2c5ae79fe5b421eb077da2ed Mon Sep 17 00:00:00 2001 From: Rohan Jadvani Date: Thu, 12 Sep 2024 22:49:11 -0400 Subject: [PATCH] feat(rust-nodejs): Create napi struct for xchacha20poly1305 key --- ironfish-rust-nodejs/index.d.ts | 17 +++ ironfish-rust-nodejs/index.js | 6 +- ironfish-rust-nodejs/src/xchacha20poly1305.rs | 134 +++++++++++++++++- 3 files changed, 155 insertions(+), 2 deletions(-) diff --git a/ironfish-rust-nodejs/index.d.ts b/ironfish-rust-nodejs/index.d.ts index ffa853a065..dabaa15b66 100644 --- a/ironfish-rust-nodejs/index.d.ts +++ b/ironfish-rust-nodejs/index.d.ts @@ -47,6 +47,9 @@ export const TRANSACTION_EXPIRATION_LENGTH: number export const TRANSACTION_FEE_LENGTH: number export const LATEST_TRANSACTION_VERSION: number export function verifyTransactions(serializedTransactions: Array): boolean +export const XCHACHA20POLY1305_KEY_LENGTH: number +export const SALT_LENGTH: number +export const XNONCE_LENGTH: number export function encrypt(plaintext: Buffer, passphrase: string): Buffer export function decrypt(encryptedBlob: Buffer, passphrase: string): Buffer export const enum LanguageCode { @@ -237,6 +240,20 @@ export class UnsignedTransaction { sign(spenderHexKey: string): Buffer addSignature(signature: Buffer): Buffer } +export type NativeXChaCha20Poly1305Key = XChaCha20Poly1305Key +export class XChaCha20Poly1305Key { + constructor(passphrase: string) + static fromParts(passphrase: string, salt: Buffer, nonce: Buffer): XChaCha20Poly1305Key + deriveKey(salt: Buffer, nonce: Buffer): XChaCha20Poly1305Key + deriveNewKey(): XChaCha20Poly1305Key + static deserialize(jsBytes: Buffer): NativeXChaCha20Poly1305Key + destroy(): void + salt(): Buffer + nonce(): Buffer + key(): Buffer + encrypt(plaintext: Buffer): Buffer + decrypt(ciphertext: Buffer): Buffer +} export class FoundBlockResult { randomness: string miningRequestId: number diff --git a/ironfish-rust-nodejs/index.js b/ironfish-rust-nodejs/index.js index 34689156e3..0244012293 100644 --- a/ironfish-rust-nodejs/index.js +++ b/ironfish-rust-nodejs/index.js @@ -252,7 +252,7 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { FishHashContext, 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 } = nativeBinding +const { FishHashContext, 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, XCHACHA20POLY1305_KEY_LENGTH, SALT_LENGTH, XNONCE_LENGTH, XChaCha20Poly1305Key, encrypt, decrypt, LanguageCode, generateKey, spendingKeyToWords, wordsToSpendingKey, generatePublicAddressFromIncomingViewKey, generateKeyFromPrivateKey, initializeSapling, FoundBlockResult, ThreadPoolHandler, isValidPublicAddress, CpuCount, getCpuCount, generateRandomizedPublicKey, multisig } = nativeBinding module.exports.FishHashContext = FishHashContext module.exports.KEY_LENGTH = KEY_LENGTH @@ -290,6 +290,10 @@ module.exports.TransactionPosted = TransactionPosted module.exports.Transaction = Transaction module.exports.verifyTransactions = verifyTransactions module.exports.UnsignedTransaction = UnsignedTransaction +module.exports.XCHACHA20POLY1305_KEY_LENGTH = XCHACHA20POLY1305_KEY_LENGTH +module.exports.SALT_LENGTH = SALT_LENGTH +module.exports.XNONCE_LENGTH = XNONCE_LENGTH +module.exports.XChaCha20Poly1305Key = XChaCha20Poly1305Key module.exports.encrypt = encrypt module.exports.decrypt = decrypt module.exports.LanguageCode = LanguageCode diff --git a/ironfish-rust-nodejs/src/xchacha20poly1305.rs b/ironfish-rust-nodejs/src/xchacha20poly1305.rs index 1135ad67b2..af6d677eee 100644 --- a/ironfish-rust-nodejs/src/xchacha20poly1305.rs +++ b/ironfish-rust-nodejs/src/xchacha20poly1305.rs @@ -2,12 +2,144 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use ironfish::xchacha20poly1305::{self, EncryptOutput}; +use ironfish::xchacha20poly1305::{ + self, EncryptOutput, XChaCha20Poly1305Key, KEY_LENGTH as KEY_SIZE, SALT_LENGTH as SALT_SIZE, + XNONCE_LENGTH as XNONCE_SIZE, +}; use napi::{bindgen_prelude::*, JsBuffer}; use napi_derive::napi; use crate::to_napi_err; +#[napi] +pub const XCHACHA20POLY1305_KEY_LENGTH: u32 = KEY_SIZE as u32; + +#[napi] +pub const SALT_LENGTH: u32 = SALT_SIZE as u32; + +#[napi] +pub const XNONCE_LENGTH: u32 = XNONCE_SIZE as u32; + +#[napi(js_name = "XChaCha20Poly1305Key")] +pub struct NativeXChaCha20Poly1305Key { + pub(crate) key: XChaCha20Poly1305Key, +} + +#[napi] +impl NativeXChaCha20Poly1305Key { + #[napi(constructor)] + pub fn generate(passphrase: String) -> Result { + let key = XChaCha20Poly1305Key::generate(passphrase.as_bytes()).map_err(to_napi_err)?; + + Ok(NativeXChaCha20Poly1305Key { key }) + } + + #[napi] + pub fn from_parts( + passphrase: String, + salt: JsBuffer, + nonce: JsBuffer, + ) -> Result { + let salt_buffer = salt.into_value()?; + let salt_vec = salt_buffer.as_ref(); + let mut salt_bytes = [0u8; SALT_SIZE]; + salt_bytes.clone_from_slice(&salt_vec[0..SALT_SIZE]); + + let nonce_buffer = nonce.into_value()?; + let nonce_vec = nonce_buffer.as_ref(); + let mut nonce_bytes = [0; XNONCE_SIZE]; + nonce_bytes.clone_from_slice(&nonce_vec[0..XNONCE_SIZE]); + + let key = XChaCha20Poly1305Key::from_parts(passphrase.as_bytes(), salt_bytes, nonce_bytes) + .map_err(to_napi_err)?; + + Ok(NativeXChaCha20Poly1305Key { key }) + } + + #[napi] + pub fn derive_key( + &self, + salt: JsBuffer, + nonce: JsBuffer, + ) -> Result { + let salt_buffer = salt.into_value()?; + let salt_vec = salt_buffer.as_ref(); + let mut salt_bytes = [0; SALT_SIZE]; + salt_bytes.clone_from_slice(&salt_vec[0..SALT_SIZE]); + + let derived_key = self.key.derive_key(salt_bytes).map_err(to_napi_err)?; + + let nonce_buffer = nonce.into_value()?; + let nonce_vec = nonce_buffer.as_ref(); + let mut nonce_bytes = [0; XNONCE_SIZE]; + nonce_bytes.clone_from_slice(&nonce_vec[0..XNONCE_SIZE]); + + let key = XChaCha20Poly1305Key { + key: derived_key, + nonce: nonce_bytes, + salt: salt_bytes, + }; + + Ok(NativeXChaCha20Poly1305Key { key }) + } + + #[napi] + pub fn derive_new_key(&self) -> Result { + let key = self.key.derive_new_key().map_err(to_napi_err)?; + + Ok(NativeXChaCha20Poly1305Key { key }) + } + + #[napi(factory)] + pub fn deserialize(js_bytes: JsBuffer) -> Result { + let byte_vec = js_bytes.into_value()?; + + let key = XChaCha20Poly1305Key::read(byte_vec.as_ref()).map_err(to_napi_err)?; + + Ok(NativeXChaCha20Poly1305Key { key }) + } + + #[napi] + pub fn destroy(&mut self) -> Result<()> { + self.key.destroy(); + Ok(()) + } + + #[napi] + pub fn salt(&self) -> Buffer { + Buffer::from(self.key.salt.to_vec()) + } + + #[napi] + pub fn nonce(&self) -> Buffer { + Buffer::from(self.key.nonce.to_vec()) + } + + #[napi] + pub fn key(&self) -> Buffer { + Buffer::from(self.key.key.to_vec()) + } + + #[napi] + pub fn encrypt(&self, plaintext: JsBuffer) -> Result { + let plaintext_bytes = plaintext.into_value()?; + let result = self + .key + .encrypt(plaintext_bytes.as_ref()) + .map_err(to_napi_err)?; + + Ok(Buffer::from(&result[..])) + } + + #[napi] + pub fn decrypt(&self, ciphertext: JsBuffer) -> Result { + let byte_vec = ciphertext.into_value()?; + let result = self.key.decrypt(byte_vec.to_vec()).map_err(to_napi_err)?; + + Ok(Buffer::from(&result[..])) + } +} + #[napi] pub fn encrypt(plaintext: JsBuffer, passphrase: String) -> Result { let plaintext_bytes = plaintext.into_value()?;