diff --git a/Cargo.lock b/Cargo.lock index cb42ba77..c869f2df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -590,6 +590,7 @@ dependencies = [ "napi", "napi-build", "napi-derive", + "num-bigint", ] [[package]] diff --git a/napi/Cargo.toml b/napi/Cargo.toml index 725d2e76..7410d6a1 100644 --- a/napi/Cargo.toml +++ b/napi/Cargo.toml @@ -24,6 +24,7 @@ napi-derive = { workspace = true } chia-wallet-sdk = { workspace = true } chia = { workspace = true } clvmr = { workspace = true } +num-bigint = { workspace = true } [build-dependencies] napi-build = "2.0.1" diff --git a/napi/index.d.ts b/napi/index.d.ts index 85561671..39fcf555 100644 --- a/napi/index.d.ts +++ b/napi/index.d.ts @@ -3,6 +3,18 @@ /* auto-generated by NAPI-RS */ +export interface Pair { + first: ClvmPtr + rest: ClvmPtr +} +export interface Output { + value: ClvmPtr + cost: bigint +} +export interface Curry { + program: ClvmPtr + args: Array +} export interface Coin { parentCoinInfo: Uint8Array puzzleHash: Uint8Array @@ -45,11 +57,11 @@ export interface NftMetadata { } export interface ParsedNft { nftInfo: NftInfo - innerPuzzle: Uint8Array + innerPuzzle: ClvmPtr } -export declare function parseNftInfo(puzzleReveal: Uint8Array): ParsedNft | null -export declare function parseUnspentNft(parentCoin: Coin, parentPuzzleReveal: Uint8Array, parentSolution: Uint8Array, coin: Coin): Nft | null -export declare function spendNft(nft: Nft, innerSpend: Spend): Array +export declare function parseNftInfo(clvm: ClvmAllocator, ptr: ClvmPtr): ParsedNft | null +export declare function parseUnspentNft(clvm: ClvmAllocator, parentCoin: Coin, parentPuzzleReveal: ClvmPtr, parentSolution: ClvmPtr, coin: Coin): Nft | null +export declare function spendNft(clvm: ClvmAllocator, nft: Nft, innerSpend: Spend): Array export interface NftMint { metadata: NftMetadata p2PuzzleHash: Uint8Array @@ -63,8 +75,36 @@ export interface MintedNfts { } export declare function mintNfts(parentCoinId: Uint8Array, nftMints: Array): MintedNfts export interface Spend { - puzzle: Uint8Array - solution: Uint8Array + puzzle: ClvmPtr + solution: ClvmPtr +} +export declare function spendP2Standard(clvm: ClvmAllocator, syntheticKey: Uint8Array, conditions: Array): Spend +export declare function spendP2Singleton(clvm: ClvmAllocator, launcherId: Uint8Array, coinId: Uint8Array, singletonInnerPuzzleHash: Uint8Array): Spend +export declare function testRoundtrip(value: bigint): bigint +export declare class ClvmAllocator { + constructor() + deserialize(value: Uint8Array): ClvmPtr + deserializeWithBackrefs(value: Uint8Array): ClvmPtr + serialize(ptr: ClvmPtr): Uint8Array + serializeWithBackrefs(ptr: ClvmPtr): Uint8Array + treeHash(ptr: ClvmPtr): Uint8Array + run(puzzle: ClvmPtr, solution: ClvmPtr, maxCost: bigint, mempoolMode: boolean): Output + curry(ptr: ClvmPtr, args: Array): ClvmPtr + uncurry(ptr: ClvmPtr): Curry | null + newList(values: Array): ClvmPtr + newPair(first: ClvmPtr, rest: ClvmPtr): ClvmPtr + newAtom(value: Uint8Array): ClvmPtr + newU32(value: number): ClvmPtr + newBigInt(value: bigint): ClvmPtr + list(ptr: ClvmPtr): Array + pair(ptr: ClvmPtr): Pair | null + atom(ptr: ClvmPtr): Uint8Array | null + atomLength(ptr: ClvmPtr): number | null + u32(ptr: ClvmPtr): number | null + bigInt(ptr: ClvmPtr): bigint | null +} +export declare class ClvmPtr { + static nil(): ClvmPtr + isAtom(): boolean + isPair(): boolean } -export declare function spendP2Standard(syntheticKey: Uint8Array, conditions: Array): Spend -export declare function spendP2Singleton(launcherId: Uint8Array, coinId: Uint8Array, singletonInnerPuzzleHash: Uint8Array): Spend diff --git a/napi/index.js b/napi/index.js index b247e8e1..d6e0fecf 100644 --- a/napi/index.js +++ b/napi/index.js @@ -310,8 +310,10 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { toCoinId, parseNftInfo, parseUnspentNft, spendNft, mintNfts, spendP2Standard, spendP2Singleton } = nativeBinding +const { ClvmAllocator, ClvmPtr, toCoinId, parseNftInfo, parseUnspentNft, spendNft, mintNfts, spendP2Standard, spendP2Singleton, testRoundtrip } = nativeBinding +module.exports.ClvmAllocator = ClvmAllocator +module.exports.ClvmPtr = ClvmPtr module.exports.toCoinId = toCoinId module.exports.parseNftInfo = parseNftInfo module.exports.parseUnspentNft = parseUnspentNft @@ -319,3 +321,4 @@ module.exports.spendNft = spendNft module.exports.mintNfts = mintNfts module.exports.spendP2Standard = spendP2Standard module.exports.spendP2Singleton = spendP2Singleton +module.exports.testRoundtrip = testRoundtrip diff --git a/napi/src/clvm.rs b/napi/src/clvm.rs new file mode 100644 index 00000000..b9db7f7f --- /dev/null +++ b/napi/src/clvm.rs @@ -0,0 +1,274 @@ +use std::mem; + +use chia::{ + clvm_traits::{ClvmDecoder, ClvmEncoder, FromClvm, ToClvm}, + clvm_utils::{tree_hash, CurriedProgram}, +}; +use chia_wallet_sdk::SpendContext; +use clvmr::{ + run_program, + serde::{node_from_bytes, node_from_bytes_backrefs, node_to_bytes, node_to_bytes_backrefs}, + ChiaDialect, NodePtr, SExp, ENABLE_BLS_OPS_OUTSIDE_GUARD, ENABLE_FIXED_DIV, MEMPOOL_MODE, +}; +use napi::bindgen_prelude::*; + +use crate::traits::{IntoJs, IntoRust}; + +#[napi] +pub struct ClvmAllocator(pub(crate) clvmr::Allocator); + +impl ClvmAllocator { + pub fn with_context(&mut self, f: impl FnOnce(&mut SpendContext) -> T) -> T { + let mut ctx = SpendContext::from(mem::take(&mut self.0)); + let result = f(&mut ctx); + self.0 = ctx.allocator; + result + } +} + +#[napi] +impl ClvmAllocator { + #[napi(constructor)] + pub fn new() -> Result { + Ok(Self(clvmr::Allocator::new())) + } + + #[napi] + pub fn deserialize(&mut self, value: Uint8Array) -> Result { + let ptr = node_from_bytes(&mut self.0, &value)?; + Ok(ClvmPtr(ptr)) + } + + #[napi] + pub fn deserialize_with_backrefs(&mut self, value: Uint8Array) -> Result { + let ptr = node_from_bytes_backrefs(&mut self.0, &value)?; + Ok(ClvmPtr(ptr)) + } + + #[napi] + pub fn serialize(&self, ptr: &ClvmPtr) -> Result { + let bytes = node_to_bytes(&self.0, ptr.0)?; + Ok(bytes.into_js().unwrap()) + } + + #[napi] + pub fn serialize_with_backrefs(&self, ptr: &ClvmPtr) -> Result { + let bytes = node_to_bytes_backrefs(&self.0, ptr.0)?; + Ok(bytes.into_js().unwrap()) + } + + #[napi] + pub fn tree_hash(&self, ptr: &ClvmPtr) -> Result { + tree_hash(&self.0, ptr.0).to_bytes().into_js() + } + + #[napi] + pub fn run( + &mut self, + env: Env, + puzzle: &ClvmPtr, + solution: &ClvmPtr, + max_cost: BigInt, + mempool_mode: bool, + ) -> Result { + let mut flags = ENABLE_BLS_OPS_OUTSIDE_GUARD | ENABLE_FIXED_DIV; + + if mempool_mode { + flags |= MEMPOOL_MODE; + } + + let result = run_program( + &mut self.0, + &ChiaDialect::new(flags), + puzzle.0, + solution.0, + max_cost.into_rust()?, + ) + .map_err(|error| Error::from_reason(error.to_string()))?; + + Ok(Output { + value: ClvmPtr(result.1).into_instance(env)?, + cost: result.0.into_js()?, + }) + } + + #[napi] + pub fn curry(&mut self, ptr: &ClvmPtr, args: Vec>) -> Result { + let mut args_ptr = self.0.one(); + + for arg in args.into_iter().rev() { + args_ptr = self + .0 + .encode_curried_arg(arg.0, args_ptr) + .map_err(|error| Error::from_reason(error.to_string()))?; + } + + CurriedProgram { + program: ptr.0, + args: args_ptr, + } + .to_clvm(&mut self.0) + .map_err(|error| Error::from_reason(error.to_string())) + .map(ClvmPtr) + } + + #[napi] + pub fn uncurry(&self, env: Env, ptr: &ClvmPtr) -> Result> { + let Ok(value) = CurriedProgram::::from_clvm(&self.0, ptr.0) else { + return Ok(None); + }; + + let mut args = Vec::new(); + let mut args_ptr = value.args; + + while let Ok((first, rest)) = self.0.decode_curried_arg(&args_ptr) { + args.push(ClvmPtr(first).into_instance(env)?); + args_ptr = rest; + } + + if self.0.small_number(args_ptr) != Some(1) { + return Ok(None); + } + + Ok(Some(Curry { + program: ClvmPtr(value.program).into_instance(env)?, + args, + })) + } + + #[napi] + pub fn new_list(&mut self, values: Vec>) -> Result { + let items: Vec = values.into_iter().map(|ptr| ptr.0).collect(); + let ptr = items + .to_clvm(&mut self.0) + .map_err(|error| Error::from_reason(error.to_string()))?; + Ok(ClvmPtr(ptr)) + } + + #[napi] + pub fn new_pair(&mut self, first: &ClvmPtr, rest: &ClvmPtr) -> Result { + let ptr = self + .0 + .new_pair(first.0, rest.0) + .map_err(|error| Error::from_reason(error.to_string()))?; + Ok(ClvmPtr(ptr)) + } + + #[napi] + pub fn new_atom(&mut self, value: Uint8Array) -> Result { + let value: Vec = value.into_rust()?; + let ptr = self + .0 + .new_atom(&value) + .map_err(|error| Error::from_reason(error.to_string()))?; + Ok(ClvmPtr(ptr)) + } + + #[napi] + pub fn new_u32(&mut self, value: u32) -> Result { + let ptr = self + .0 + .new_small_number(value) + .map_err(|error| Error::from_reason(error.to_string()))?; + Ok(ClvmPtr(ptr)) + } + + #[napi] + pub fn new_big_int(&mut self, value: BigInt) -> Result { + let value = value.into_rust()?; + let ptr = self + .0 + .new_number(value) + .map_err(|error| Error::from_reason(error.to_string()))?; + Ok(ClvmPtr(ptr)) + } + + #[napi] + pub fn list(&self, env: Env, ptr: &ClvmPtr) -> Result>> { + let items = Vec::::from_clvm(&self.0, ptr.0) + .map_err(|error| Error::from_reason(error.to_string()))?; + items + .into_iter() + .map(|ptr| ClvmPtr(ptr).into_instance(env)) + .collect() + } + + #[napi] + pub fn pair(&self, env: Env, ptr: &ClvmPtr) -> Result> { + let SExp::Pair(first, rest) = self.0.sexp(ptr.0) else { + return Ok(None); + }; + Ok(Some(Pair { + first: ClvmPtr(first).into_instance(env)?, + rest: ClvmPtr(rest).into_instance(env)?, + })) + } + + #[napi] + pub fn atom(&self, ptr: &ClvmPtr) -> Option { + match self.0.sexp(ptr.0) { + SExp::Atom => Some(self.0.atom(ptr.0).as_ref().to_vec().into_js().unwrap()), + SExp::Pair(..) => None, + } + } + + #[napi] + pub fn atom_length(&self, ptr: &ClvmPtr) -> Option { + match self.0.sexp(ptr.0) { + SExp::Atom => self.0.atom_len(ptr.0).try_into().ok(), + SExp::Pair(..) => None, + } + } + + #[napi] + pub fn u32(&self, ptr: &ClvmPtr) -> Option { + self.0.small_number(ptr.0) + } + + #[napi] + pub fn big_int(&self, ptr: &ClvmPtr) -> Option { + match self.0.sexp(ptr.0) { + SExp::Atom => Some(self.0.number(ptr.0).into_js().unwrap()), + SExp::Pair(..) => None, + } + } +} + +#[napi] +pub struct ClvmPtr(pub(crate) clvmr::NodePtr); + +#[napi] +impl ClvmPtr { + #[napi(factory)] + pub fn nil() -> Self { + Self(NodePtr::NIL) + } + + #[napi] + pub fn is_atom(&self) -> bool { + self.0.is_atom() + } + + #[napi] + pub fn is_pair(&self) -> bool { + self.0.is_pair() + } +} + +#[napi(object)] +pub struct Pair { + pub first: ClassInstance, + pub rest: ClassInstance, +} + +#[napi(object)] +pub struct Output { + pub value: ClassInstance, + pub cost: BigInt, +} + +#[napi(object)] +pub struct Curry { + pub program: ClassInstance, + pub args: Vec>, +} diff --git a/napi/src/lib.rs b/napi/src/lib.rs index 5299d824..7d55f695 100644 --- a/napi/src/lib.rs +++ b/napi/src/lib.rs @@ -5,6 +5,7 @@ #[macro_use] extern crate napi_derive; +mod clvm; mod coin; mod coin_spend; mod lineage_proof; @@ -13,9 +14,18 @@ mod nft_mint; mod spend; mod traits; +pub use clvm::*; pub use coin::*; pub use coin_spend::*; pub use lineage_proof::*; pub use nft::*; pub use nft_mint::*; pub use spend::*; + +use traits::{IntoJs, IntoRust}; + +#[napi] +pub fn test_roundtrip(value: napi::bindgen_prelude::BigInt) -> napi::bindgen_prelude::BigInt { + let num: num_bigint::BigInt = value.into_rust().unwrap(); + num.into_js().unwrap() +} diff --git a/napi/src/nft.rs b/napi/src/nft.rs index 977c6ed3..0f7e3386 100644 --- a/napi/src/nft.rs +++ b/napi/src/nft.rs @@ -1,14 +1,10 @@ use chia::puzzles::nft; -use chia_wallet_sdk::{self as sdk, Primitive, SpendContext}; -use clvmr::{ - serde::{node_from_bytes, node_to_bytes}, - Allocator, -}; +use chia_wallet_sdk::{self as sdk, Primitive}; use napi::bindgen_prelude::*; use crate::{ traits::{FromJs, IntoJs, IntoRust}, - Coin, CoinSpend, LineageProof, Spend, + ClvmAllocator, ClvmPtr, Coin, CoinSpend, LineageProof, Spend, }; #[napi(object)] @@ -125,45 +121,40 @@ impl FromJs for nft::NftMetadata { #[napi(object)] pub struct ParsedNft { pub nft_info: NftInfo, - pub inner_puzzle: Uint8Array, + pub inner_puzzle: ClassInstance, } #[napi] -pub fn parse_nft_info(puzzle_reveal: Uint8Array) -> Result> { - let mut allocator = Allocator::new(); - let ptr = node_from_bytes(&mut allocator, puzzle_reveal.as_ref())?; - let puzzle = sdk::Puzzle::parse(&allocator, ptr); - - let Some((nft_info, inner_puzzle)) = - sdk::NftInfo::::parse(&allocator, puzzle) - .map_err(|error| Error::from_reason(error.to_string()))? +pub fn parse_nft_info(env: Env, clvm: &ClvmAllocator, ptr: &ClvmPtr) -> Result> { + let puzzle = sdk::Puzzle::parse(&clvm.0, ptr.0); + + let Some((nft_info, inner_puzzle)) = sdk::NftInfo::::parse(&clvm.0, puzzle) + .map_err(|error| Error::from_reason(error.to_string()))? else { return Ok(None); }; Ok(Some(ParsedNft { nft_info: nft_info.into_js()?, - inner_puzzle: node_to_bytes(&allocator, inner_puzzle.ptr())?.into(), + inner_puzzle: ClvmPtr(inner_puzzle.ptr()).into_instance(env)?, })) } #[napi] pub fn parse_unspent_nft( + clvm: &mut ClvmAllocator, parent_coin: Coin, - parent_puzzle_reveal: Uint8Array, - parent_solution: Uint8Array, + parent_puzzle_reveal: &ClvmPtr, + parent_solution: &ClvmPtr, coin: Coin, ) -> Result> { - let mut allocator = Allocator::new(); - let parent_ptr = node_from_bytes(&mut allocator, parent_puzzle_reveal.as_ref())?; - let parent_puzzle = sdk::Puzzle::parse(&allocator, parent_ptr); - let parent_solution = node_from_bytes(&mut allocator, parent_solution.as_ref())?; + let parent_puzzle = sdk::Puzzle::parse(&clvm.0, parent_puzzle_reveal.0); let Some(nft) = sdk::Nft::::from_parent_spend( - &mut allocator, + &mut clvm.0, parent_coin.into_rust()?, parent_puzzle, - parent_solution, + parent_solution.0, coin.into_rust()?, ) .map_err(|error| Error::from_reason(error.to_string()))? @@ -175,15 +166,19 @@ pub fn parse_unspent_nft( } #[napi] -pub fn spend_nft(nft: Nft, inner_spend: Spend) -> Result> { - let mut ctx = SpendContext::new(); - - let nft = sdk::Nft::::from_js(nft)?; - let puzzle = node_from_bytes(&mut ctx.allocator, &inner_spend.puzzle)?; - let solution = node_from_bytes(&mut ctx.allocator, &inner_spend.solution)?; - - nft.spend(&mut ctx, sdk::Spend { puzzle, solution }) +pub fn spend_nft(clvm: &mut ClvmAllocator, nft: Nft, inner_spend: Spend) -> Result> { + clvm.with_context(|ctx| { + let nft = sdk::Nft::::from_js(nft)?; + + nft.spend( + ctx, + sdk::Spend { + puzzle: inner_spend.puzzle.0, + solution: inner_spend.solution.0, + }, + ) .map_err(|error| Error::from_reason(error.to_string()))?; - ctx.take().into_iter().map(IntoJs::into_js).collect() + ctx.take().into_iter().map(IntoJs::into_js).collect() + }) } diff --git a/napi/src/spend.rs b/napi/src/spend.rs index de0f4c6e..86bdc111 100644 --- a/napi/src/spend.rs +++ b/napi/src/spend.rs @@ -1,68 +1,72 @@ use chia::{bls::PublicKey, clvm_traits::FromClvm}; -use chia_wallet_sdk::{ - Condition, Conditions, P2Singleton, SpendContext, SpendWithConditions, StandardLayer, -}; -use clvmr::{ - serde::{node_from_bytes, node_to_bytes}, - NodePtr, -}; +use chia_wallet_sdk::{Condition, Conditions, P2Singleton, SpendWithConditions, StandardLayer}; +use clvmr::NodePtr; use napi::bindgen_prelude::*; -use crate::traits::{FromJs, IntoRust}; +use crate::{ + traits::{FromJs, IntoRust}, + ClvmAllocator, ClvmPtr, +}; #[napi(object)] pub struct Spend { - pub puzzle: Uint8Array, - pub solution: Uint8Array, + pub puzzle: ClassInstance, + pub solution: ClassInstance, } #[napi] -pub fn spend_p2_standard(synthetic_key: Uint8Array, conditions: Vec) -> Result { - let mut ctx = SpendContext::new(); - - let synthetic_key = PublicKey::from_js(synthetic_key)?; - let p2 = StandardLayer::new(synthetic_key); +pub fn spend_p2_standard( + env: Env, + clvm: &mut ClvmAllocator, + synthetic_key: Uint8Array, + conditions: Vec>, +) -> Result { + clvm.with_context(|ctx| { + let synthetic_key = PublicKey::from_js(synthetic_key)?; + let p2 = StandardLayer::new(synthetic_key); - let mut spend_conditions = Conditions::new(); + let mut spend_conditions = Conditions::new(); - for condition in conditions { - let condition = node_from_bytes(&mut ctx.allocator, &condition)?; - spend_conditions = spend_conditions.with( - Condition::::from_clvm(&ctx.allocator, condition) - .map_err(|error| Error::from_reason(error.to_string()))?, - ); - } + for condition in conditions { + spend_conditions = spend_conditions.with( + Condition::::from_clvm(&ctx.allocator, condition.0) + .map_err(|error| Error::from_reason(error.to_string()))?, + ); + } - let spend = p2 - .spend_with_conditions(&mut ctx, spend_conditions) - .map_err(|error| Error::from_reason(error.to_string()))?; + let spend = p2 + .spend_with_conditions(ctx, spend_conditions) + .map_err(|error| Error::from_reason(error.to_string()))?; - Ok(Spend { - puzzle: node_to_bytes(&ctx.allocator, spend.puzzle)?.into(), - solution: node_to_bytes(&ctx.allocator, spend.solution)?.into(), + Ok(Spend { + puzzle: ClvmPtr(spend.puzzle).into_instance(env)?, + solution: ClvmPtr(spend.solution).into_instance(env)?, + }) }) } #[napi] pub fn spend_p2_singleton( + env: Env, + clvm: &mut ClvmAllocator, launcher_id: Uint8Array, coin_id: Uint8Array, singleton_inner_puzzle_hash: Uint8Array, ) -> Result { - let mut ctx = SpendContext::new(); - - let p2 = P2Singleton::new(launcher_id.into_rust()?); + clvm.with_context(|ctx| { + let p2 = P2Singleton::new(launcher_id.into_rust()?); - let spend = p2 - .spend( - &mut ctx, - coin_id.into_rust()?, - singleton_inner_puzzle_hash.into_rust()?, - ) - .map_err(|error| Error::from_reason(error.to_string()))?; + let spend = p2 + .spend( + ctx, + coin_id.into_rust()?, + singleton_inner_puzzle_hash.into_rust()?, + ) + .map_err(|error| Error::from_reason(error.to_string()))?; - Ok(Spend { - puzzle: node_to_bytes(&ctx.allocator, spend.puzzle)?.into(), - solution: node_to_bytes(&ctx.allocator, spend.solution)?.into(), + Ok(Spend { + puzzle: ClvmPtr(spend.puzzle).into_instance(env)?, + solution: ClvmPtr(spend.solution).into_instance(env)?, + }) }) } diff --git a/napi/src/traits.rs b/napi/src/traits.rs index b4ed1258..85770bdd 100644 --- a/napi/src/traits.rs +++ b/napi/src/traits.rs @@ -57,6 +57,18 @@ impl FromJs for [u8; N] { } } +impl IntoJs for Vec { + fn into_js(self) -> Result { + Ok(Uint8Array::new(self)) + } +} + +impl FromJs for Vec { + fn from_js(js_value: Uint8Array) -> Result { + Ok(js_value.to_vec()) + } +} + impl IntoJs for PublicKey { fn into_js(self) -> Result { Ok(Uint8Array::new(self.to_bytes().to_vec())) @@ -64,10 +76,7 @@ impl IntoJs for PublicKey { } impl FromJs for PublicKey { - fn from_js(js_value: Uint8Array) -> Result - where - Self: Sized, - { + fn from_js(js_value: Uint8Array) -> Result { PublicKey::from_bytes(&js_value.into_rust()?) .map_err(|error| Error::from_reason(error.to_string())) } @@ -96,3 +105,70 @@ impl FromJs for u64 { Ok(value) } } + +impl FromJs for num_bigint::BigInt { + fn from_js(num: BigInt) -> Result { + if num.words.is_empty() { + return Ok(num_bigint::BigInt::ZERO); + } + + // Convert u64 words into a big-endian byte array + let bytes = words_to_bytes(&num.words); + + // Create the BigInt from the bytes + let bigint = num_bigint::BigInt::from_bytes_be( + if num.sign_bit { + num_bigint::Sign::Minus + } else { + num_bigint::Sign::Plus + }, + &bytes, + ); + + Ok(bigint) + } +} + +impl IntoJs for num_bigint::BigInt { + fn into_js(self) -> Result { + let (sign, bytes) = self.to_bytes_be(); + + // Convert the byte array into u64 words + let words = bytes_to_words(&bytes); + + Ok(BigInt { + sign_bit: sign == num_bigint::Sign::Minus, + words, + }) + } +} + +/// Helper function to convert Vec (words) into Vec (byte array) +fn words_to_bytes(words: &[u64]) -> Vec { + let mut bytes = Vec::with_capacity(words.len() * 8); + for word in words { + bytes.extend_from_slice(&word.to_be_bytes()); + } + + // Remove leading zeros from the byte array + while let Some(0) = bytes.first() { + bytes.remove(0); + } + + bytes +} + +/// Helper function to convert Vec (byte array) into Vec (words) +fn bytes_to_words(bytes: &[u8]) -> Vec { + let mut padded_bytes = vec![0u8; (8 - bytes.len() % 8) % 8]; + padded_bytes.extend_from_slice(bytes); + + let mut words = Vec::with_capacity(padded_bytes.len() / 8); + + for chunk in padded_bytes.chunks(8) { + let word = u64::from_be_bytes(chunk.try_into().unwrap()); + words.push(word); + } + + words +}