diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/.DS_Store differ diff --git a/CMakeLists.txt b/CMakeLists.txt index c7850b9..8bf2da1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,19 +1,19 @@ -#******************************************************************************* -#* (c) 2018-2024 Zondax AG -#* -#* Licensed under the Apache License, Version 2.0 (the "License"); -#* you may not use this file except in compliance with the License. -#* You may obtain a copy of the License at -#* -#* http://www.apache.org/licenses/LICENSE-2.0 -#* -#* Unless required by applicable law or agreed to in writing, software -#* distributed under the License is distributed on an "AS IS" BASIS, -#* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -#* See the License for the specific language governing permissions and -#* limitations under the License. -#******************************************************************************** -cmake_minimum_required(VERSION 3.28) +# ******************************************************************************* +# * (c) 2018-2024 Zondax AG +# * +# * Licensed under the Apache License, Version 2.0 (the "License"); +# * you may not use this file except in compliance with the License. +# * You may obtain a copy of the License at +# * +# * http://www.apache.org/licenses/LICENSE-2.0 +# * +# * Unless required by applicable law or agreed to in writing, software +# * distributed under the License is distributed on an "AS IS" BASIS, +# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# * See the License for the specific language governing permissions and +# * limitations under the License. +# ******************************************************************************** +cmake_minimum_required(VERSION 3.22) include("cmake/HunterGate.cmake") HunterGate( URL "https://github.com/cpp-pm/hunter/archive/v0.25.5.tar.gz" @@ -22,20 +22,20 @@ HunterGate( ) if(CMAKE_GENERATOR MATCHES "Ninja") - message(FATAL_ERROR "This project does not support the Ninja generator. " - "Please use Unix Makefiles or another supported generator. " - "This error is typical in CLion. In this case, switch to generator Unix Makefiles.") + message(FATAL_ERROR "This project does not support the Ninja generator. " + "Please use Unix Makefiles or another supported generator. " + "This error is typical in CLion. In this case, switch to generator Unix Makefiles.") endif() -######################################################## +# ####################################################### project(ledger-ironfish VERSION 0.0.0) set(CMAKE_CXX_STANDARD 20) cmake_policy(SET CMP0025 NEW) -cmake_policy(SET CMP0144 NEW) +# cmake_policy(SET CMP0144 NEW) set(HUNTER_STATUS_DEBUG ON) set(HUNTER_TLS_VERIFY OFF) - +add_compile_options(-gdwarf-4) enable_testing() option(ENABLE_FUZZING "Build with fuzzing instrumentation and build fuzz targets" OFF) @@ -60,22 +60,22 @@ if(ENABLE_FUZZING) SET(ENABLE_SANITIZERS ON CACHE BOOL "Sanitizer automatically enabled" FORCE) SET(CMAKE_BUILD_TYPE Debug) - if (DEFINED ENV{FUZZ_LOGGING}) + if(DEFINED ENV{FUZZ_LOGGING}) add_definitions(-DFUZZING_LOGGING) message(FATAL_ERROR "Fuzz logging enabled") endif() set(CMAKE_CXX_CLANG_TIDY clang-tidy -checks=-*,bugprone-*,cert-*,clang-analyzer-*,-cert-err58-cpp,misc-*) - if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 10.0) + if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 10.0) message(FATAL_ERROR "Clang version must be at least 10.0!") endif() else() message(FATAL_ERROR - "You are using an unsupported compiler! Fuzzing only works with Clang 10.\n" - "1. Install clang-10 \n" - "2. Pass -DCMAKE_C_COMPILER=clang-10 -DCMAKE_CXX_COMPILER=clang++-10") + "You are using an unsupported compiler! Fuzzing only works with Clang 10.\n" + "1. Install clang-10 \n" + "2. Pass -DCMAKE_C_COMPILER=clang-10 -DCMAKE_CXX_COMPILER=clang++-10") endif() string(APPEND CMAKE_C_FLAGS " -fsanitize=fuzzer-no-link") @@ -95,67 +95,68 @@ if(ENABLE_SANITIZERS) string(APPEND CMAKE_LINKER_FLAGS " -fsanitize=address,undefined -fsanitize-recover=address,undefined") endif() -set (RETRIEVE_MAJOR_CMD - "cat ${CMAKE_CURRENT_SOURCE_DIR}/app/Makefile.version | grep APPVERSION_M | cut -b 14- | tr -d '\n'" +set(RETRIEVE_MAJOR_CMD + "cat ${CMAKE_CURRENT_SOURCE_DIR}/app/Makefile.version | grep APPVERSION_M | cut -b 14- | tr -d '\n'" ) -set (RETRIEVE_MINOR_CMD - "cat ${CMAKE_CURRENT_SOURCE_DIR}/app/Makefile.version | grep APPVERSION_N | cut -b 14- | tr -d '\n'" +set(RETRIEVE_MINOR_CMD + "cat ${CMAKE_CURRENT_SOURCE_DIR}/app/Makefile.version | grep APPVERSION_N | cut -b 14- | tr -d '\n'" ) execute_process( - COMMAND bash "-c" ${RETRIEVE_MAJOR_CMD} - RESULT_VARIABLE MAJOR_RESULT - OUTPUT_VARIABLE MAJOR_VERSION + COMMAND bash "-c" ${RETRIEVE_MAJOR_CMD} + RESULT_VARIABLE MAJOR_RESULT + OUTPUT_VARIABLE MAJOR_VERSION ) execute_process( - COMMAND bash "-c" ${RETRIEVE_MINOR_CMD} - RESULT_VARIABLE MINOR_RESULT - OUTPUT_VARIABLE MINOR_VERSION + COMMAND bash "-c" ${RETRIEVE_MINOR_CMD} + RESULT_VARIABLE MINOR_RESULT + OUTPUT_VARIABLE MINOR_VERSION ) -message(STATUS "LEDGER_MAJOR_VERSION [${MAJOR_RESULT}]: ${MAJOR_VERSION}" ) -message(STATUS "LEDGER_MINOR_VERSION [${MINOR_RESULT}]: ${MINOR_VERSION}" ) +message(STATUS "LEDGER_MAJOR_VERSION [${MAJOR_RESULT}]: ${MAJOR_VERSION}") +message(STATUS "LEDGER_MINOR_VERSION [${MINOR_RESULT}]: ${MINOR_VERSION}") add_definitions( -DLEDGER_MAJOR_VERSION=${MAJOR_VERSION} -DLEDGER_MINOR_VERSION=${MINOR_VERSION} ) - # ############################################################# # ############################################################# # Static Libraries file(GLOB_RECURSE LIB_SRC - ${CMAKE_CURRENT_SOURCE_DIR}/deps/ledger-zxlib/src/app_mode.c - ${CMAKE_CURRENT_SOURCE_DIR}/deps/ledger-zxlib/src/base58.c - ${CMAKE_CURRENT_SOURCE_DIR}/deps/ledger-zxlib/src/bech32.c - ${CMAKE_CURRENT_SOURCE_DIR}/deps/ledger-zxlib/src/bignum.c - ${CMAKE_CURRENT_SOURCE_DIR}/deps/ledger-zxlib/src/hexutils.c - ${CMAKE_CURRENT_SOURCE_DIR}/deps/ledger-zxlib/src/zxmacros.c - ${CMAKE_CURRENT_SOURCE_DIR}/deps/ledger-zxlib/src/zxformat.c - #### - ${CMAKE_CURRENT_SOURCE_DIR}/app/src/parser.c - ${CMAKE_CURRENT_SOURCE_DIR}/app/src/parser_impl.c - ${CMAKE_CURRENT_SOURCE_DIR}/app/src/parser_impl_common.c - ${CMAKE_CURRENT_SOURCE_DIR}/app/src/crypto_helper.c - #### - ${CMAKE_CURRENT_SOURCE_DIR}/deps/blake2/ref/blake2b-ref.c - ${CMAKE_CURRENT_SOURCE_DIR}/app/src/blake2s/blake2s-ref.c - ) + ${CMAKE_CURRENT_SOURCE_DIR}/deps/ledger-zxlib/src/app_mode.c + ${CMAKE_CURRENT_SOURCE_DIR}/deps/ledger-zxlib/src/base58.c + ${CMAKE_CURRENT_SOURCE_DIR}/deps/ledger-zxlib/src/bech32.c + ${CMAKE_CURRENT_SOURCE_DIR}/deps/ledger-zxlib/src/bignum.c + ${CMAKE_CURRENT_SOURCE_DIR}/deps/ledger-zxlib/src/hexutils.c + ${CMAKE_CURRENT_SOURCE_DIR}/deps/ledger-zxlib/src/zxmacros.c + ${CMAKE_CURRENT_SOURCE_DIR}/deps/ledger-zxlib/src/zxformat.c + + # ### + ${CMAKE_CURRENT_SOURCE_DIR}/app/src/parser.c + ${CMAKE_CURRENT_SOURCE_DIR}/app/src/parser_impl.c + ${CMAKE_CURRENT_SOURCE_DIR}/app/src/parser_impl_common.c + ${CMAKE_CURRENT_SOURCE_DIR}/app/src/crypto_helper.c + ${CMAKE_CURRENT_SOURCE_DIR}/app/src/parser_utils.c + + # ### + ${CMAKE_CURRENT_SOURCE_DIR}/deps/blake2/ref/blake2b-ref.c + ${CMAKE_CURRENT_SOURCE_DIR}/app/src/blake2s/blake2s-ref.c +) add_library(app_lib STATIC ${LIB_SRC}) target_include_directories(app_lib PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/deps/ledger-zxlib/include - ${CMAKE_CURRENT_SOURCE_DIR}/app/src - ${CMAKE_CURRENT_SOURCE_DIR}/app/src/lib - ${CMAKE_CURRENT_SOURCE_DIR}/app/src/common - ${CMAKE_CURRENT_SOURCE_DIR}/app/rust/include - ${CMAKE_CURRENT_SOURCE_DIR}/deps/blake2/ref - - ) + ${CMAKE_CURRENT_SOURCE_DIR}/deps/ledger-zxlib/include + ${CMAKE_CURRENT_SOURCE_DIR}/app/src + ${CMAKE_CURRENT_SOURCE_DIR}/app/src/lib + ${CMAKE_CURRENT_SOURCE_DIR}/app/src/common + ${CMAKE_CURRENT_SOURCE_DIR}/app/rust/include + ${CMAKE_CURRENT_SOURCE_DIR}/deps/blake2/ref +) -############################################################## -## Rust library for CPP tests +# ############################################################# +# # Rust library for CPP tests set(RUST_LIB_DIR "${CMAKE_CURRENT_SOURCE_DIR}/app/rust") # Determine the Rust target triple based on the host system @@ -204,39 +205,41 @@ add_dependencies(rslib RustLibBuild) # Ensure your C++ targets depend on the Rust library being built first # For example, for your app_lib static library: add_dependencies(app_lib rslib) -############################################################## -# Tests + +# ############################################################# +# Tests file(GLOB_RECURSE TESTS_SRC - ${CMAKE_CURRENT_SOURCE_DIR}/tests/*.cpp) + ${CMAKE_CURRENT_SOURCE_DIR}/tests/*.cpp) add_executable(unittests ${TESTS_SRC}) target_include_directories(unittests PRIVATE - ${gtest_SOURCE_DIR}/include - ${gmock_SOURCE_DIR}/include - ${CMAKE_CURRENT_SOURCE_DIR}/app/src - ${CMAKE_CURRENT_SOURCE_DIR}/app/src/lib - ### - ${CMAKE_CURRENT_SOURCE_DIR}/deps/blake2/ref - ) + ${gtest_SOURCE_DIR}/include + ${gmock_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR}/app/src + ${CMAKE_CURRENT_SOURCE_DIR}/app/src/lib + + # ## + ${CMAKE_CURRENT_SOURCE_DIR}/deps/blake2/ref +) target_link_libraries(unittests PRIVATE - app_lib - rslib - GTest::gtest_main - fmt::fmt - JsonCpp::JsonCpp) + app_lib + rslib + GTest::gtest_main + fmt::fmt + JsonCpp::JsonCpp) add_compile_definitions(TESTVECTORS_DIR="${CMAKE_CURRENT_SOURCE_DIR}/tests/") add_test(NAME unittests COMMAND unittests) set_tests_properties(unittests PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests) -############################################################## -############################################################## -# Fuzz Targets +# ############################################################# +# ############################################################# +# Fuzz Targets if(ENABLE_FUZZING) set(FUZZ_TARGETS parser_parse - ) + ) foreach(target ${FUZZ_TARGETS}) add_executable(fuzz-${target} ${CMAKE_CURRENT_SOURCE_DIR}/fuzz/${target}.cpp) diff --git a/app/rust/Cargo.lock b/app/rust/Cargo.lock index 61621b6..717a95a 100644 --- a/app/rust/Cargo.lock +++ b/app/rust/Cargo.lock @@ -2,6 +2,28 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "bitvec" version = "1.0.1" @@ -14,13 +36,113 @@ dependencies = [ "wyz", ] +[[package]] +name = "blake2b_simd" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "blake2s_simd" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94230421e395b9920d23df13ea5d77a20e1725331f90fbbf6df6040b33f756ae" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + [[package]] name = "bls12_381" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7bc6d6292be3a19e6379786dac800f551e5865a5bb51ebbe3064ab80433f403" dependencies = [ - "ff", + "ff 0.13.0", + "rand_core", + "subtle", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "cpufeatures" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core", + "typenum", +] + +[[package]] +name = "ff" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +dependencies = [ + "bitvec", "rand_core", "subtle", ] @@ -41,17 +163,58 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "group" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +dependencies = [ + "ff 0.12.1", + "rand_core", + "subtle", +] + [[package]] name = "group" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ - "ff", + "ff 0.13.0", "rand_core", "subtle", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "jubjub" version = "0.10.0" @@ -60,18 +223,63 @@ checksum = "8499f7a74008aafbecb2a2e608a3e13e4dd3e84df198b604451efe93f2de6e61" dependencies = [ "bitvec", "bls12_381", - "ff", - "group", + "ff 0.13.0", + "group 0.13.0", "rand_core", "subtle", ] +[[package]] +name = "libc" +version = "0.2.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "panic-halt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de96540e0ebde571dc55c73d60ef407c653844e6f9a1e2fdbd40c07b9252d812" +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "radium" version = "0.7.0" @@ -83,12 +291,22 @@ name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] [[package]] name = "rslib" version = "0.1.0" dependencies = [ + "arrayref", + "blake2b_simd", + "blake2s_simd", + "chacha20poly1305", + "ff 0.12.1", + "group 0.12.1", "jubjub", + "nom", "panic-halt", ] @@ -104,6 +322,34 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wyz" version = "0.5.1" @@ -112,3 +358,9 @@ checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" dependencies = [ "tap", ] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/app/rust/Cargo.toml b/app/rust/Cargo.toml index b60bd4f..74213fc 100644 --- a/app/rust/Cargo.toml +++ b/app/rust/Cargo.toml @@ -13,6 +13,13 @@ crate-type = ["staticlib"] [dependencies] jubjub = { version = "0.10.0", default-features = false } +group = "0.12.0" +chacha20poly1305 = "0.10.1" +blake2b_simd = { version = "1.0.0", default-features = false } +blake2s_simd = { version = "1.0.0", default-features = false } +nom = { version = "7.1.3", default-features = false } +arrayref = { version = "0.3.8", default-features = false } +ff = "0.12.0" [target.thumbv6m-none-eabi.dev-dependencies] panic-halt = "0.2.0" diff --git a/app/rust/src/crypto.rs b/app/rust/src/crypto.rs new file mode 100644 index 0000000..e25daed --- /dev/null +++ b/app/rust/src/crypto.rs @@ -0,0 +1,12 @@ +mod encryption_keys; +//mod epk; +//mod keys; +mod utils; + +pub use encryption_keys::*; +//pub use epk::Epk; +//#[cfg(feature = "ledger")] +//pub(crate) use keys::get_dkg_keys; +//pub(crate) use keys::multisig_to_key_type; +//pub use keys::ConstantKey; +pub use utils::*; diff --git a/app/rust/src/crypto/chacha20poly.rst b/app/rust/src/crypto/chacha20poly.rst new file mode 100644 index 0000000..64ffe83 --- /dev/null +++ b/app/rust/src/crypto/chacha20poly.rst @@ -0,0 +1,37 @@ +use crate::{rand::LedgerRng, AppSW}; +use alloc::vec; +use alloc::vec::Vec; +use chacha20poly1305::{ + aead::{Aead, KeyInit}, + ChaCha20Poly1305, Key, Nonce, +}; +#[cfg(feature = "ledger")] +use ledger_device_sdk::ecc::{bip32_derive, ChainCode, CurvesId, Secret}; +// #[cfg(feature = "ledger")] +// use ledger_device_sdk::random::LedgerRng; + +pub const NONCE_LEN: usize = 12; +pub const KEY_LEN: usize = 32; + +#[inline(never)] +pub fn decrypt(key: &[u8; 32], payload: &[u8], nonce: &[u8]) -> Result, AppSW> { + zlog_stack("start decrypt\0"); + + // Generate a random key + let key = Key::clone_from_slice(key); + + // Create a ChaCha20Poly1305 instance + let cipher = ChaCha20Poly1305::new(&key); + + let nonce_slice = <&[u8; NONCE_LEN]>::try_from(nonce).map_err(|_| AppSW::InvalidPayload)?; + + // Generate a random nonce + let nonce = Nonce::clone_from_slice(nonce_slice); // 96-bits; unique per message + + // Encrypt the message with associated data + let ciphertext = cipher + .decrypt(&nonce, payload) + .map_err(|_| AppSW::DecryptionFail)?; + + Ok(ciphertext) +} diff --git a/app/rust/src/crypto/encryption_keys.rs b/app/rust/src/crypto/encryption_keys.rs new file mode 100644 index 0000000..be46661 --- /dev/null +++ b/app/rust/src/crypto/encryption_keys.rs @@ -0,0 +1,41 @@ +use blake2b_simd::Params as Blake2b; +use jubjub::{AffinePoint, Scalar}; + +use crate::{ + ironfish::{constants::SHARED_KEY_PERSONALIZATION, view_keys::OutgoingViewKey}, + parser::KEY_LENGTH, +}; + +/// Calculate the key used to encrypt the shared keys for an [`crate::outputs::OutputDescription`]. +/// +/// The shared keys are encrypted using the outgoing viewing key for the +/// spender (the person creating the note owned by the receiver). This gets +/// combined with hashes of the output values to make a key unique to, and +/// signed by, the output. +/// +/// Naming is getting a bit far-fetched here because it's the keys used to +/// encrypt other keys. Keys, all the way down! +#[inline(never)] +pub fn calculate_key_for_encryption_keys( + outgoing_view_key: &OutgoingViewKey, + value_commitment: &AffinePoint, + // note_commitment: &Scalar, + note_commitment: &[u8; 32], + // public_key: &SubgroupPoint, + public_key: &[u8; KEY_LENGTH], +) -> [u8; 32] { + let mut key_input = [0u8; 128]; + key_input[0..32].copy_from_slice(&outgoing_view_key.view_key); + key_input[32..64].copy_from_slice(&value_commitment.to_bytes()); + // key_input[64..96].copy_from_slice(¬e_commitment.to_bytes()); + key_input[64..96].copy_from_slice(note_commitment); + key_input[96..128].copy_from_slice(public_key); + + Blake2b::new() + .hash_length(32) + .personal(SHARED_KEY_PERSONALIZATION) + .hash(&key_input) + .as_bytes() + .try_into() + .expect("has has incorrect length") +} diff --git a/app/rust/src/crypto/epk.rst b/app/rust/src/crypto/epk.rst new file mode 100644 index 0000000..e129f11 --- /dev/null +++ b/app/rust/src/crypto/epk.rst @@ -0,0 +1,3 @@ +use crate::parser::KEY_LENGTH; + +pub struct Epk<'a>(&'a [u8; KEY_LENGTH]); diff --git a/app/rust/src/crypto/keys.rst b/app/rust/src/crypto/keys.rst new file mode 100644 index 0000000..172bea4 --- /dev/null +++ b/app/rust/src/crypto/keys.rst @@ -0,0 +1,87 @@ +/***************************************************************************** + * Ledger App Ironfish Rust. + * (c) 2023 Ledger SAS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ + +use crate::bolos::zlog_stack; +use crate::ironfish::multisig::{derive_account_keys, MultisigAccountKeys}; +#[cfg(feature = "ledger")] +use crate::nvm::dkg_keys::DkgKeys; +use crate::AppSW; +use alloc::vec::Vec; +#[cfg(feature = "ledger")] +use ledger_device_sdk::io::{Comm, Event}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ConstantKey { + SpendingKeyGenerator, + ProofGenerationKeyGenerator, + PublicKeyGenerator, +} + +#[cfg(feature = "ledger")] +pub(crate) fn get_dkg_keys() -> Result { + zlog_stack("start handler_dkg_get_keys\0"); + + let group_secret_key = DkgKeys.load_group_secret_key()?; + let frost_public_key_package = DkgKeys.load_frost_public_key_package()?; + + let verifying_key: [u8; 32] = frost_public_key_package + .verifying_key() + .serialize() + .map_err(|_| AppSW::InvalidKeyType)? + .as_slice() + .try_into() + .map_err(|_| AppSW::InvalidKeyType)?; + + Ok(derive_account_keys(&verifying_key, &group_secret_key)) +} + +#[inline(never)] +pub(crate) fn multisig_to_key_type( + account_keys: &MultisigAccountKeys, + key_type: u8, +) -> Result, AppSW> { + zlog_stack("start get_requested_keys\0"); + + let mut resp: Vec = Vec::with_capacity(32 * 4); + match key_type { + 0 => { + let data = account_keys.public_address.public_address(); + resp.extend_from_slice(&data); + + Ok(resp) + } + 1 => { + resp.extend_from_slice(account_keys.view_key.authorizing_key.to_bytes().as_ref()); + resp.extend_from_slice( + account_keys + .view_key + .nullifier_deriving_key + .to_bytes() + .as_ref(), + ); + resp.extend_from_slice(account_keys.incoming_viewing_key.view_key.as_ref()); + resp.extend_from_slice(account_keys.outgoing_viewing_key.view_key.as_ref()); + Ok(resp) + } + 2 => { + resp.extend_from_slice(account_keys.view_key.authorizing_key.to_bytes().as_ref()); + resp.extend_from_slice(account_keys.proof_authorizing_key.to_bytes().as_ref()); + Ok(resp) + } + _ => Err(AppSW::InvalidKeyType), + } +} diff --git a/app/rust/src/crypto/ovk.rst b/app/rust/src/crypto/ovk.rst new file mode 100644 index 0000000..d70bdc4 --- /dev/null +++ b/app/rust/src/crypto/ovk.rst @@ -0,0 +1,38 @@ +use blake2b_simd::Params as Blake2b; + +const PERSONALIZATION: &[u8; 16] = b"Iron Fish Money "; + +use jubjub::{ExtendedPoint, Fr as Scalar}; + +use crate::parser::{ParserError, KEY_LENGTH}; + +// As defined by: +// https://ironfish.network/learn/whitepaper/protocol/accounts +#[derive(Clone, Debug)] +pub struct OutgoingViewKey([u8; KEY_LENGTH]); + +impl OutgoingViewKey { + pub fn from_secret(secret_key: &Scalar) -> Result { + // 64-bytes as per documentation + let result: [u8; 2 * KEY_LENGTH] = Blake2b::new() + .hash_length(64) + .personal(PERSONALIZATION) + .to_state() + .update(&secret_key.to_bytes()) + .update(&[2]) + .finalize() + .as_bytes() + .try_into() + .map_err(|_| ParserError::InvalidKey)?; + + // Take the first 32 bytes (256 bits) of the 64-byte (512-bit) hash + let mut ovk = [0u8; 32]; + ovk.copy_from_slice(&result[..32]); + + Ok(Self(ovk)) + } + + pub fn to_bytes(&self) -> [u8; 32] { + self.0 + } +} diff --git a/app/rust/src/crypto/utils.rs b/app/rust/src/crypto/utils.rs new file mode 100644 index 0000000..2ee4132 --- /dev/null +++ b/app/rust/src/crypto/utils.rs @@ -0,0 +1,89 @@ +use chacha20poly1305::{ChaCha20Poly1305, Key, KeyInit, Nonce}; +use core::convert::TryFrom; +use ff::PrimeField; +use group::cofactor::CofactorGroup; +use group::Group; +use jubjub::{AffinePoint, ExtendedPoint, Fq, Fr, Scalar}; +use nom::bytes::complete::take; + +use crate::ironfish::constants::{ + PROOF_GENERATION_KEY_GENERATOR, PUBLIC_KEY_GENERATOR, SPENDING_KEY_GENERATOR, +}; +use crate::ironfish::errors::IronfishError; +use crate::parser::ParserError; + +pub fn parse_affine_point(raw_bytes: &[u8; 32]) -> Result { + AffinePoint::from_bytes(*raw_bytes) + .into_option() + .ok_or(ParserError::UnexpectedValue) +} + +pub fn parse_extended_point(raw_bytes: &[u8; 32]) -> Result { + parse_affine_point(raw_bytes).map(ExtendedPoint::from) +} + +/// Decrypt the encrypted text using the given key and ciphertext, also checking +/// that the mac tag is correct. + +pub(crate) fn decrypt( + key: &[u8; 32], + ciphertext: &[u8], +) -> Result<[u8; SIZE], IronfishError> { + use chacha20poly1305::AeadInPlace; + + let decryptor = ChaCha20Poly1305::new(Key::from_slice(key)); + + let mut plaintext = [0u8; SIZE]; + plaintext.copy_from_slice(&ciphertext[..SIZE]); + + decryptor + .decrypt_in_place_detached( + &Nonce::default(), + &[], + &mut plaintext, + ciphertext[SIZE..].into(), + ) + .map_err(|_| IronfishError::InvalidDecryptionKey)?; + + Ok(plaintext) +} + +/// Reads a PrimeField element from a byte array, valid PrimeField elements are: +/// Fr: Fr::Repr is [u8; 32] +/// Scalar: Scalar::Repr is [u8; 32] +/// Fp: Fp::Repr is [u8; 32] +/// Fq: Fq::Repr is [u8; 32] +macro_rules! generate_from_bytes_conversion { + ($type:ty, $func_name:ident) => { + pub fn $func_name(bytes: &[u8]) -> Result<(&[u8], $type), ParserError> { + let (rem, raw) = take(32usize)(bytes)?; + let bytes = arrayref::array_ref!(raw, 0, 32); + <$type>::from_bytes(bytes) + .into_option() + .ok_or(ParserError::InvalidScalar) + .map(|f| (rem, f)) + } + }; +} + +// Generates functions +generate_from_bytes_conversion!(Fr, read_fr); +generate_from_bytes_conversion!(Fq, read_fq); +generate_from_bytes_conversion!(Scalar, read_scalar); + +#[cfg(test)] +mod utils { + use super::*; + + const EXTENDED_POINT: &str = "247f750514f0a0018af8fc17ef85ad376fa92390603bf9f8b8cb1597d57d7d52"; + + #[test] + fn parse_extended_as_affine() { + let raw_extended = hex::decode(EXTENDED_POINT).unwrap(); + let raw_extended: [u8; 32] = raw_extended.try_into().unwrap(); + + let affine = parse_affine_point(&raw_extended).unwrap(); + + assert_eq!(raw_extended, affine.to_bytes()); + } +} diff --git a/app/rust/src/ironfish.rs b/app/rust/src/ironfish.rs new file mode 100644 index 0000000..e9f6dea --- /dev/null +++ b/app/rust/src/ironfish.rs @@ -0,0 +1,6 @@ +pub mod constants; +pub mod errors; +//pub mod multisig; +pub mod public_address; +pub mod sapling; +pub mod view_keys; diff --git a/app/rust/src/ironfish/constants.rs b/app/rust/src/ironfish/constants.rs new file mode 100644 index 0000000..4d4c14f --- /dev/null +++ b/app/rust/src/ironfish/constants.rs @@ -0,0 +1,59 @@ +use jubjub::{AffineNielsPoint, AffinePoint, Fq}; + +pub const TX_HASH_LEN: usize = 32; + +pub const IDENTITY_LEN: usize = 129; + +pub const MAX_PARTICIPANTS: u8 = 4; + +pub const SPENDING_KEY_GENERATOR: AffineNielsPoint = AffinePoint::from_raw_unchecked( + Fq::from_raw([ + 0x47bf_4692_0a95_a753, + 0xd5b9_a7d3_ef8e_2827, + 0xd418_a7ff_2675_3b6a, + 0x0926_d4f3_2059_c712, + ]), + Fq::from_raw([ + 0x3056_32ad_aaf2_b530, + 0x6d65_674d_cedb_ddbc, + 0x53bb_37d0_c21c_fd05, + 0x57a1_019e_6de9_b675, + ]), +) +.to_niels(); + +pub const PROOF_GENERATION_KEY_GENERATOR: AffineNielsPoint = AffinePoint::from_raw_unchecked( + Fq::from_raw([ + 0x3af2_dbef_b96e_2571, + 0xadf2_d038_f2fb_b820, + 0x7043_03f1_e890_6081, + 0x1457_a502_31cd_e2df, + ]), + Fq::from_raw([ + 0x467a_f9f7_e05d_e8e7, + 0x50df_51ea_f5a1_49d2, + 0xdec9_0184_0f49_48cc, + 0x54b6_d107_18df_2a7a, + ]), +) +.to_niels(); + +pub const PUBLIC_KEY_GENERATOR: AffineNielsPoint = AffinePoint::from_raw_unchecked( + Fq::from_raw([ + 0x3edc_c85f_4d1a_44cd, + 0x77ff_8c90_a9a0_d8f4, + 0x0daf_03b5_47e2_022b, + 0x6dad_65e6_2328_d37a, + ]), + Fq::from_raw([ + 0x5095_1f1f_eff0_8278, + 0xf0b7_03d5_3a3e_dd4e, + 0xca01_f580_9c00_eee2, + 0x6996_932c_ece1_f4bb, + ]), +) +.to_niels(); +/// BLAKE2s Personalization for CRH^ivk = BLAKE2s(ak | nk) +pub const CRH_IVK_PERSONALIZATION: &[u8; 8] = b"Zcashivk"; + +pub const SHARED_KEY_PERSONALIZATION: &[u8; 16] = b"Iron Fish Keyenc"; diff --git a/app/rust/src/ironfish/errors.rs b/app/rust/src/ironfish/errors.rs new file mode 100644 index 0000000..443bde6 --- /dev/null +++ b/app/rust/src/ironfish/errors.rs @@ -0,0 +1,54 @@ +/// Error type to handle all errors within the code and dependency-raised +/// errors. This serves 2 purposes. The first is to keep a consistent error type +/// in the code to reduce the cognitive load needed for using Result and Error +/// types. The second is to give a singular type to convert into NAPI errors to +/// be raised on the Javascript side. +#[derive(Debug, PartialEq)] +pub enum IronfishError { + BellpersonSynthesis, + CryptoBox, + FrostLibError, + FailedArgon2Hash, + FailedSignatureAggregation, + FailedSignatureVerification, + FailedXChaCha20Poly1305Decryption, + FailedXChaCha20Poly1305Encryption, + IllegalValue, + InconsistentWitness, + InvalidAssetIdentifier, + InvalidAuthorizingKey, + InvalidBalance, + InvalidCommitment, + InvalidData, + InvalidDecryptionKey, + InvalidDiversificationPoint, + InvalidEntropy, + InvalidFr, + InvalidScalar, + InvalidLanguageEncoding, + InvalidMinersFeeTransaction, + InvalidMintProof, + InvalidMintSignature, + InvalidMnemonicString, + InvalidNonceLength, + InvalidNullifierDerivingKey, + InvalidOutputProof, + InvalidPaymentAddress, + InvalidPublicAddress, + InvalidSecret, + InvalidRandomizer, + InvalidSignature, + InvalidSigningKey, + InvalidSpendProof, + InvalidSpendSignature, + InvalidTransaction, + InvalidTransactionVersion, + InvalidViewingKey, + InvalidWord, + Io, + IsSmallOrder, + RandomnessError, + RoundTwoSigningFailure, + TryFromInt, + Utf8, +} diff --git a/app/rust/src/ironfish/multisig.rst b/app/rust/src/ironfish/multisig.rst new file mode 100644 index 0000000..5edd691 --- /dev/null +++ b/app/rust/src/ironfish/multisig.rst @@ -0,0 +1,61 @@ +use crate::ironfish::constants::PROOF_GENERATION_KEY_GENERATOR; +use crate::ironfish::public_address::PublicAddress; +use crate::ironfish::sapling::SaplingKey; +use crate::ironfish::view_keys::{IncomingViewKey, OutgoingViewKey, ViewKey}; +use jubjub::{AffinePoint, Fr}; + +pub struct MultisigAccountKeys { + /// Equivalent to [`crate::keys::SaplingKey::proof_authorizing_key`] + pub proof_authorizing_key: jubjub::Fr, + /// Equivalent to [`crate::keys::SaplingKey::outgoing_viewing_key`] + pub outgoing_viewing_key: OutgoingViewKey, + /// Equivalent to [`crate::keys::SaplingKey::view_key`] + pub view_key: ViewKey, + /// Equivalent to [`crate::keys::SaplingKey::incoming_viewing_key`] + pub incoming_viewing_key: IncomingViewKey, + /// Equivalent to [`crate::keys::SaplingKey::public_address`] + pub public_address: PublicAddress, +} + +pub fn derive_account_keys( + authorizing_key: &[u8; 32], //&VerifyingKey, + group_secret_key: &[u8; 32], +) -> MultisigAccountKeys { + // Group secret key (gsk), obtained from the multisig setup process + let group_secret_key = + SaplingKey::new(*group_secret_key).expect("failed to derive group secret key"); + + // Authorization key (ak), obtained from the multisig setup process + let authorizing_key = Option::from(AffinePoint::from_bytes(*authorizing_key)) + .expect("failied to derive authorizing key"); + + // Nullifier keys (nsk and nk), derived from the gsk + let proof_authorizing_key = Fr::from(group_secret_key.sapling_proof_generation_key().nsk); + let nullifier_deriving_key_ep = + PROOF_GENERATION_KEY_GENERATOR.multiply_bits(&proof_authorizing_key.to_bytes()); + let nullifier_deriving_key = AffinePoint::from(&nullifier_deriving_key_ep); + + // Incoming view key (ivk), derived from the ak and the nk + let view_key = ViewKey { + authorizing_key, + nullifier_deriving_key, + }; + let incoming_viewing_key = IncomingViewKey { + view_key: SaplingKey::hash_viewing_key(&authorizing_key, &nullifier_deriving_key) + .expect("failed to derive view key"), + }; + + // Outgoing view key (ovk), derived from the gsk + let outgoing_viewing_key = group_secret_key.outgoing_view_key().clone(); + + // Public address (pk), derived from the ivk + let public_address = incoming_viewing_key.public_address(); + + MultisigAccountKeys { + proof_authorizing_key, + outgoing_viewing_key, + view_key, + incoming_viewing_key, + public_address, + } +} diff --git a/app/rust/src/ironfish/public_address.rs b/app/rust/src/ironfish/public_address.rs new file mode 100644 index 0000000..9152fe3 --- /dev/null +++ b/app/rust/src/ironfish/public_address.rs @@ -0,0 +1,66 @@ +use core::fmt::{self, Display, Formatter}; +use core::ptr::addr_of_mut; + +use crate::crypto::parse_affine_point; +use crate::ironfish::constants::PUBLIC_KEY_GENERATOR; +use crate::ironfish::errors::IronfishError; +use crate::ironfish::sapling::SaplingKey; +use crate::ironfish::view_keys::IncomingViewKey; +use crate::parser::FromBytes; +use arrayref::array_ref; +use jubjub::AffinePoint; +use nom::bytes::complete::take; + +pub const PUBLIC_ADDRESS_SIZE: usize = 32; + +/// The address to which funds can be sent, stored as a public +/// transmission key. Using the incoming_viewing_key allows +/// the creation of a unique public addresses without revealing the viewing key. +#[derive(Clone, Copy)] +pub struct PublicAddress(pub(crate) AffinePoint); + +impl<'a> FromBytes<'a> for PublicAddress { + #[inline(never)] + fn from_bytes_into( + input: &'a [u8], + out: &mut core::mem::MaybeUninit, + ) -> Result<&'a [u8], nom::Err> { + let (rem, raw) = take(PUBLIC_ADDRESS_SIZE)(input)?; + let raw = array_ref![raw, 0, PUBLIC_ADDRESS_SIZE]; + let point = parse_affine_point(raw)?; + + let out = out.as_mut_ptr(); + + unsafe { + addr_of_mut!((*out).0).write(point); + } + + Ok(rem) + } +} + +impl PublicAddress { + /// Initialize a public address from its 32 byte representation. + pub fn new(bytes: &[u8; PUBLIC_ADDRESS_SIZE]) -> Result { + Option::from(AffinePoint::from_bytes(*bytes)) + .map(PublicAddress) + .ok_or_else(|| IronfishError::InvalidPaymentAddress) + } + + /// Initialize a public address from a sapling key. Typically constructed from + /// SaplingKey::public_address() + pub fn from_key(sapling_key: &SaplingKey) -> PublicAddress { + Self::from_view_key(sapling_key.incoming_view_key()) + } + + pub fn from_view_key(view_key: &IncomingViewKey) -> PublicAddress { + let extended_point = PUBLIC_KEY_GENERATOR.multiply_bits(&view_key.view_key); + let result = AffinePoint::from(&extended_point); + PublicAddress(result) + } + + /// Retrieve the public address in byte form. + pub fn public_address(&self) -> [u8; PUBLIC_ADDRESS_SIZE] { + self.0.to_bytes() + } +} diff --git a/app/rust/src/ironfish/sapling.rs b/app/rust/src/ironfish/sapling.rs new file mode 100644 index 0000000..e3ea148 --- /dev/null +++ b/app/rust/src/ironfish/sapling.rs @@ -0,0 +1,175 @@ +use crate::ironfish::constants::{ + CRH_IVK_PERSONALIZATION, PROOF_GENERATION_KEY_GENERATOR, SPENDING_KEY_GENERATOR, +}; +use crate::ironfish::errors::IronfishError; +use crate::ironfish::view_keys::{IncomingViewKey, OutgoingViewKey, ProofGenerationKey, ViewKey}; +use blake2b_simd::Params as Blake2b; +use blake2s_simd::Params as Blake2s; +use jubjub::AffinePoint; + +const EXPANDED_SPEND_BLAKE2_KEY: &[u8; 16] = b"Iron Fish Money "; + +pub const SPEND_KEY_SIZE: usize = 32; + +/// A single private key generates multiple other key parts that can +/// be used to allow various forms of access to a commitment note: +/// +/// While the key parts are all represented as 256 bit keys to the outside +/// world, inside the API they map to Edwards points or scalar values +/// on the JubJub curve. +pub struct SaplingKey { + /// The private (secret) key from which all the other key parts are derived. + /// The expanded form of this key is required before a note can be spent. + spending_key: [u8; SPEND_KEY_SIZE], + + /// Part of the expanded form of the spending key, generally referred to as + /// `ask` in the literature. Derived from spending key using a seeded + /// pseudorandom hash function. Used to construct authorizing_key. + pub(crate) spend_authorizing_key: jubjub::Fr, + + /// Part of the expanded form of the spending key, generally referred to as + /// `nsk` in the literature. Derived from spending key using a seeded + /// pseudorandom hash function. Used to construct nullifier_deriving_key + pub(crate) proof_authorizing_key: jubjub::Fr, + + /// Part of the expanded form of the spending key, as well as being used + /// directly in the full viewing key. Generally referred to as + /// `ovk` in the literature. Derived from spending key using a seeded + /// pseudorandom hash function. This allows the creator of a note to access + /// keys needed to decrypt the note's contents. + pub(crate) outgoing_viewing_key: OutgoingViewKey, + + /// Part of the full viewing key. Contains ak/nk from literature, used for deriving nullifiers + /// and therefore spends + pub(crate) view_key: ViewKey, + + /// Part of the payment_address. Generally referred to as + /// `ivk` in the literature. Derived from authorizing key and + /// nullifier deriving key. Used to construct payment address and + /// transmission key. This key allows the receiver of a note to decrypt its + /// contents. Derived from view_key contents, this is materialized for convenience + pub(crate) incoming_viewing_key: IncomingViewKey, +} + +impl SaplingKey { + /// Construct a new key from an array of bytes + pub fn new(spending_key: [u8; SPEND_KEY_SIZE]) -> Result { + // ask + let spend_authorizing_key = + jubjub::Fr::from_bytes_wide(&Self::convert_key(spending_key, 0)); + + if spend_authorizing_key == jubjub::Fr::zero() { + return Err(IronfishError::IllegalValue); + } + + // nsk + let proof_authorizing_key = + jubjub::Fr::from_bytes_wide(&Self::convert_key(spending_key, 1)); + + // ovk + let mut outgoing_viewing_key = [0; SPEND_KEY_SIZE]; + outgoing_viewing_key[0..SPEND_KEY_SIZE] + .clone_from_slice(&Self::convert_key(spending_key, 2)[0..SPEND_KEY_SIZE]); + let outgoing_viewing_key = OutgoingViewKey { + view_key: outgoing_viewing_key, + }; + // ak + let authorizing_key = AffinePoint::from( + SPENDING_KEY_GENERATOR.multiply_bits(&spend_authorizing_key.to_bytes()), + ); + //nk + let nullifier_deriving_key = AffinePoint::from( + PROOF_GENERATION_KEY_GENERATOR.multiply_bits(&proof_authorizing_key.to_bytes()), + ); + let view_key = ViewKey { + authorizing_key, + nullifier_deriving_key, + }; + // ivk + let incoming_viewing_key = IncomingViewKey { + view_key: Self::hash_viewing_key(&authorizing_key, &nullifier_deriving_key)?, + }; + + Ok(SaplingKey { + spending_key, + spend_authorizing_key, + proof_authorizing_key, + outgoing_viewing_key, + view_key, + incoming_viewing_key, + }) + } + + /// Convert the spending key to another value using a pseudorandom hash + /// function. Used during key construction to derive the following keys: + /// * `spend_authorizing_key` (represents a sapling scalar Fs type) + /// * `proof_authorizing_key` (represents a sapling scalar Fs type) + /// * `outgoing_viewing_key (just some bytes) + /// + /// # Arguments + /// * `spending_key` The 32 byte spending key + /// * `modifier` a byte to add to tweak the hash for each of the three + /// values + fn convert_key(spending_key: [u8; SPEND_KEY_SIZE], modifier: u8) -> [u8; 64] { + let mut hasher = Blake2b::new() + .hash_length(64) + .personal(EXPANDED_SPEND_BLAKE2_KEY) + .to_state(); + + hasher.update(&spending_key); + hasher.update(&[modifier]); + let mut hash_result = [0; 64]; + hash_result[0..64].clone_from_slice(&hasher.finalize().as_ref()[0..64]); + hash_result + } + + /// Helper method to construct the viewing key from the authorizing key + /// and nullifier deriving key using a blake2 hash of their respective bytes. + /// + /// This method is only called once, but it's kind of messy, so I pulled it + /// out of the constructor for easier maintenance. + pub fn hash_viewing_key( + authorizing_key: &AffinePoint, + nullifier_deriving_key: &AffinePoint, + ) -> Result<[u8; 32], IronfishError> { + let mut view_key_contents = [0; 64]; + view_key_contents[0..32].copy_from_slice(&authorizing_key.to_bytes()); + view_key_contents[32..64].copy_from_slice(&nullifier_deriving_key.to_bytes()); + // let mut hasher = Blake2s::with_params(32, &[], &[], CRH_IVK_PERSONALIZATION); + + let mut hash_result = [0; 32]; + hash_result.copy_from_slice( + Blake2s::new() + .hash_length(32) + .personal(CRH_IVK_PERSONALIZATION) + .hash(&view_key_contents) + .as_bytes(), + ); + // Drop the last five bits, so it can be interpreted as a scalar. + hash_result[31] &= 0b0000_0111; + if hash_result == [0; 32] { + return Err(IronfishError::InvalidViewingKey); + } + + Ok(hash_result) + } + + /// Retrieve the publicly visible outgoing viewing key + pub fn outgoing_view_key(&self) -> &OutgoingViewKey { + &self.outgoing_viewing_key + } + + /// Retrieve the publicly visible incoming viewing key + pub fn incoming_view_key(&self) -> &IncomingViewKey { + &self.incoming_viewing_key + } + + /// Adapter to convert this key to a proof generation key for use in + /// sapling functions + pub fn sapling_proof_generation_key(&self) -> ProofGenerationKey { + ProofGenerationKey { + ak: self.view_key.authorizing_key, + nsk: self.proof_authorizing_key, + } + } +} diff --git a/app/rust/src/ironfish/view_keys.rs b/app/rust/src/ironfish/view_keys.rs new file mode 100644 index 0000000..e940550 --- /dev/null +++ b/app/rust/src/ironfish/view_keys.rs @@ -0,0 +1,140 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +//! View keys allow your transactions to be read +//! by a third party without giving the option to spend your +//! coins. This was designed for auditing systems, but may have other purposes +//! such as in the use of light clients. +//! +//! There are two kinds of view keys. One allows you to share transactions +//! that you have received, while the other allows you to share transactions +//! that you have spent. +//! + +use blake2b_simd::Params as Blake2b; +use jubjub::AffinePoint; + +use crate::ironfish::public_address::PublicAddress; + +const DIFFIE_HELLMAN_PERSONALIZATION: &[u8; 16] = b"Iron Fish shared"; + +/// Key that allows someone to view a transaction that you have received. +/// +/// Referred to as `ivk` in the literature. +#[derive(Clone)] +pub struct IncomingViewKey { + pub(crate) view_key: [u8; 32], +} + +impl IncomingViewKey { + /// Generate a public address from the incoming viewing key + pub fn public_address(&self) -> PublicAddress { + PublicAddress::from_view_key(self) + } +} +/// Contains two keys that are required (along with outgoing view key) +/// to have full view access to an account. +/// Referred to as `ViewingKey` in the literature. +#[derive(Clone)] +pub struct ViewKey { + /// Part of the full viewing key. Generally referred to as + /// `ak` in the literature. Derived from spend_authorizing_key using scalar + /// multiplication in Sapling. Used to construct incoming viewing key. + pub authorizing_key: jubjub::AffinePoint, + /// Part of the full viewing key. Generally referred to as + /// `nk` in the literature. Derived from proof_authorizing_key using scalar + /// multiplication. Used to construct incoming viewing key. + pub nullifier_deriving_key: jubjub::AffinePoint, +} + +/// Key that allows someone to view a transaction that you have spent. +/// +/// Referred to as `ovk` in the literature. +#[derive(Clone)] +pub struct OutgoingViewKey { + pub(crate) view_key: [u8; 32], +} + +#[cfg(test)] +impl OutgoingViewKey { + pub fn new(bytes: [u8; 32]) -> Self { + Self { view_key: bytes } + } +} + +#[derive(Clone)] +pub struct ProofGenerationKey { + pub ak: jubjub::AffinePoint, + pub nsk: jubjub::Fr, +} + +/// Derive a shared secret key from a secret key and the other person's public +/// key. +/// +/// The shared secret point is calculated by multiplying the public and private +/// keys. This gets converted to bytes and hashed together with the reference +/// public key to generate the final shared secret as used in encryption. + +/// A Diffie Hellman key exchange might look like this: +/// * Alice generates her DH secret key as SaplingKeys::internal_viewing_key +/// * Alice publishes her Public key +/// * This becomes her DH public_key +/// * Bob chooses some randomness as his secret key +/// * Bob's public key is calculated as (PUBLIC_KEY_GENERATOR * Bob secret key) +/// * This public key becomes the reference public key for both sides +/// * Bob sends public key to Alice +/// * Bob calculates shared secret key as (Alice public key * Bob secret key) +/// * which is (Alice public key * Bob secret key) +/// * which is equivalent to (Alice internal viewing key * PUBLIC_KEY_GENERATOR * Bob secret key) +/// * Alice calculates shared secret key as (Bob public key * Alice internal viewing key) +/// * which is equivalent to (Alice internal viewing key * PUBLIC_KEY_GENERATOR * Bob secret key) +/// * both Alice and Bob hash the shared secret key with the reference public +/// key (Bob's public key) to get the final shared secret +/// +/// The resulting key can be used in any symmetric cipher +#[must_use] +pub(crate) fn shared_secret( + secret_key: &jubjub::Fr, + other_public_key: &AffinePoint, // Previously a SubgroupPoint + reference_public_key: &AffinePoint, +) -> [u8; 32] { + let shared_secret = other_public_key * secret_key; // ExtendedPoint + // Because we are not using ExtendedPoint but AffinePoint, lets convert it + let affine = AffinePoint::from(&shared_secret).to_bytes(); + hash_shared_secret(&affine, reference_public_key) +} + +#[must_use] +fn hash_shared_secret(shared_secret: &[u8; 32], reference_public_key: &AffinePoint) -> [u8; 32] { + let reference_bytes = reference_public_key.to_bytes(); + + let mut hasher = Blake2b::new() + .hash_length(32) + .personal(DIFFIE_HELLMAN_PERSONALIZATION) + .to_state(); + + hasher.update(&shared_secret[..]); + hasher.update(&reference_bytes); + + let mut hash_result = [0; 32]; + hash_result[..].copy_from_slice(hasher.finalize().as_ref()); + hash_result +} + +///// Equivalent to calling `shared_secret()` multiple times on the same +///// `other_public_key`/`reference_public_key`, but more efficient. +//#[must_use] +//pub(crate) fn shared_secrets( +// secret_keys: &[[u8; 32]], +// other_public_key: &AffinePoint, +// reference_public_key: &AffinePoint, +//) -> Vec<[u8; 32]> { +// let shared_secrets = other_public_key.as_extended().multiply_many(secret_keys); +// shared_secrets +// .into_iter() +// .map(move |shared_secret| { +// hash_shared_secret(&shared_secret.to_bytes(), reference_public_key) +// }) +// .collect() +//} diff --git a/app/rust/src/lib.rs b/app/rust/src/lib.rs index dfd4a28..c9b2dd6 100644 --- a/app/rust/src/lib.rs +++ b/app/rust/src/lib.rs @@ -14,24 +14,17 @@ * limitations under the License. ********************************************************************************/ #![no_std] +#![no_alloc] #![no_builtins] #![allow(dead_code, unused_imports)] use core::panic::PanicInfo; -use constants::{SPENDING_KEY_GENERATOR}; -mod constants; +pub mod crypto; +pub mod ironfish; +pub mod parser; -use jubjub::{Fr, AffinePoint, ExtendedPoint}; - -// ParserError should mirror parser_error_t from parser_common. -// At the moment, just implement OK or Error -#[repr(C)] -#[derive(PartialEq, Debug)] -pub enum ParserError { - ParserOk = 0, - ParserUnexpectedError = 5, -} +use jubjub::{AffinePoint, ExtendedPoint, Fr}; #[repr(C)] pub enum ConstantKey { @@ -48,11 +41,17 @@ pub extern "C" fn from_bytes_wide(input: &[u8; 64], output: &mut [u8; 32]) -> Pa } #[no_mangle] -pub extern "C" fn scalar_multiplication(input: &[u8; 32], key: ConstantKey, output: *mut [u8; 32]) -> ParserError { +pub extern "C" fn scalar_multiplication( + input: &[u8; 32], + key: ConstantKey, + output: *mut [u8; 32], +) -> ParserError { let key_point = match key { - ConstantKey::SpendingKeyGenerator => constants::SPENDING_KEY_GENERATOR, - ConstantKey::ProofGenerationKeyGenerator => constants::PROOF_GENERATION_KEY_GENERATOR, - ConstantKey::PublicKeyGenerator => constants::PUBLIC_KEY_GENERATOR, + ConstantKey::SpendingKeyGenerator => utils::constants::SPENDING_KEY_GENERATOR, + ConstantKey::ProofGenerationKeyGenerator => { + utils::constants::PROOF_GENERATION_KEY_GENERATOR + } + ConstantKey::PublicKeyGenerator => utils::constants::PUBLIC_KEY_GENERATOR, }; let extended_point = key_point.multiply_bits(input); @@ -67,8 +66,11 @@ pub extern "C" fn scalar_multiplication(input: &[u8; 32], key: ConstantKey, outp } #[no_mangle] -pub extern "C" fn randomizeKey(key: &[u8; 32], randomness: &[u8; 32], output: &mut [u8; 32]) -> ParserError { - +pub extern "C" fn randomizeKey( + key: &[u8; 32], + randomness: &[u8; 32], + output: &mut [u8; 32], +) -> ParserError { let mut skfr = Fr::from_bytes(key).unwrap(); let alphafr = Fr::from_bytes(randomness).unwrap(); skfr += alphafr; @@ -78,7 +80,12 @@ pub extern "C" fn randomizeKey(key: &[u8; 32], randomness: &[u8; 32], output: &m } #[no_mangle] -pub extern "C" fn compute_sbar( s: &[u8; 32], r: &[u8; 32], rsk: &[u8; 32], sbar: &mut [u8; 32]) -> ParserError{ +pub extern "C" fn compute_sbar( + s: &[u8; 32], + r: &[u8; 32], + rsk: &[u8; 32], + sbar: &mut [u8; 32], +) -> ParserError { let s_point = Fr::from_bytes(s).unwrap(); let r_point = Fr::from_bytes(r).unwrap(); let rsk_point = Fr::from_bytes(rsk).unwrap(); @@ -89,6 +96,16 @@ pub extern "C" fn compute_sbar( s: &[u8; 32], r: &[u8; 32], rsk: &[u8; 32], s ParserError::ParserOk } +#[no_mangle] +pub extern "C" fn decrypt_note(note: *const u8, note_len: usize, ovk: &[u8; 32]) -> ParserError { + if note.is_null() || note_len == 0 { + return ParserError::ParserUnexpectedError; // Handle null or zero length + } + let note_slice = unsafe { core::slice::from_raw_parts(note, note_len as usize) }; + + MerkelNote::from_bytes(note_slice); + ParserError::ParserOk +} #[cfg(not(test))] #[panic_handler] diff --git a/app/rust/src/parser.rs b/app/rust/src/parser.rs new file mode 100644 index 0000000..adc565a --- /dev/null +++ b/app/rust/src/parser.rs @@ -0,0 +1,15 @@ +mod asset_identifier; +mod constants; +mod error; +mod from_bytes; +mod memo; +mod merkle_note; +mod note; + +pub use asset_identifier::AssetIdentifier; +pub use constants::*; +pub use error::ParserError; +pub use from_bytes::FromBytes; +pub use memo::Memo; +pub use merkle_note::MerkleNote; +pub use note::Note; diff --git a/app/rust/src/parser/asset_identifier.rs b/app/rust/src/parser/asset_identifier.rs new file mode 100644 index 0000000..ef694a3 --- /dev/null +++ b/app/rust/src/parser/asset_identifier.rs @@ -0,0 +1,47 @@ +use core::fmt::{self, Display, Formatter}; +use core::ptr::addr_of_mut; + +use arrayref::array_ref; +use nom::bytes::complete::take; + +use crate::parser::FromBytes; + +use crate::parser::constants::ASSET_ID_LENGTH; + +/// A convenience wrapper around an asset id byte-array, allowing us to push the +/// error checking of the asset id validity to instantiation +/// instead of when trying to get the generator point. This causes code relating +/// to notes and value commitments to be a bit cleaner +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +pub struct AssetIdentifier([u8; ASSET_ID_LENGTH]); + +// impl AssetIdentifier { +// pub fn as_bytes(&self) -> &[u8; ASSET_ID_LENGTH] { +// &self.0 +// } +// } + +// impl Display for AssetIdentifier { +// fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { +// write!(f, "{}", hex::encode(&self.0)) +// } +// } + +impl<'a> FromBytes<'a> for AssetIdentifier { + #[inline(never)] + fn from_bytes_into( + input: &'a [u8], + out: &mut core::mem::MaybeUninit, + ) -> Result<&'a [u8], nom::Err> { + let (rem, raw) = take(ASSET_ID_LENGTH)(input)?; + let bytes = array_ref!(raw, 0, ASSET_ID_LENGTH); + + let out = out.as_mut_ptr(); + + unsafe { + addr_of_mut!((*out).0).write(*bytes); + } + + Ok(rem) + } +} diff --git a/app/rust/src/constants.rs b/app/rust/src/parser/constants.rs similarity index 75% rename from app/rust/src/constants.rs rename to app/rust/src/parser/constants.rs index 687c472..17f44ee 100644 --- a/app/rust/src/constants.rs +++ b/app/rust/src/parser/constants.rs @@ -63,3 +63,20 @@ pub const PUBLIC_KEY_GENERATOR: AffineNielsPoint = AffinePoint::from_raw_uncheck ]), ) .to_niels(); + +pub const ENCRYPTED_SHARED_KEY_SIZE: usize = 64; +pub const SCALAR_SIZE: usize = 32; +pub const MEMO_SIZE: usize = 32; +pub const AMOUNT_VALUE_SIZE: usize = 8; +pub const ASSET_ID_LENGTH: usize = 32; +pub const PUBLIC_ADDRESS_SIZE: usize = 32; +pub const MAC_SIZE: usize = 16; +pub const AFFINE_POINT_SIZE: usize = 32; +pub const KEY_LENGTH: usize = 32; + +// Size of a merkle note +// https://github.com/iron-fish/ironfish/blob/master/ironfish-rust/src/note.rs#L30 +pub const ENCRYPTED_NOTE_SIZE: usize = + SCALAR_SIZE + MEMO_SIZE + AMOUNT_VALUE_SIZE + ASSET_ID_LENGTH + PUBLIC_ADDRESS_SIZE; + +pub const NOTE_ENCRYPTION_KEY_SIZE: usize = ENCRYPTED_SHARED_KEY_SIZE + MAC_SIZE; diff --git a/app/rust/src/parser/error.rs b/app/rust/src/parser/error.rs new file mode 100644 index 0000000..e4c4395 --- /dev/null +++ b/app/rust/src/parser/error.rs @@ -0,0 +1,65 @@ +use nom::error::ErrorKind; + +#[repr(u32)] +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum ParserError { + Ok = 0, + UnexpectedBufferEnd, + ValueOutOfRange, + OperationOverflows, + UnexpectedValue, + UnexpectedType, + InvalidTxVersion, + InvalidKey, + InvalidAffinePoint, + InvalidScalar, + + InvalidTypeId, + + InvalidSpend, + InvalidOuptut, + InvalidMint, + InvalidBurn, + + UnexpectedError, +} + +impl From for ParserError { + fn from(err: ErrorKind) -> Self { + match err { + ErrorKind::Eof => ParserError::UnexpectedBufferEnd, + ErrorKind::Permutation => ParserError::UnexpectedType, + ErrorKind::TooLarge => ParserError::ValueOutOfRange, + ErrorKind::Tag => ParserError::InvalidTypeId, + _ => ParserError::UnexpectedError, + } + } +} + +impl nom::error::ParseError for ParserError { + fn from_error_kind(_input: I, kind: ErrorKind) -> Self { + Self::from(kind) + } + + // We don't have enough memory resources to use here an array with the last + // N errors to be used as a backtrace, so that, we just propagate here the latest + // reported error + fn append(_input: I, _kind: ErrorKind, other: Self) -> Self { + other + } +} +impl From for nom::Err { + fn from(error: ParserError) -> Self { + nom::Err::Error(error) + } +} + +impl From> for ParserError { + fn from(e: nom::Err) -> Self { + match e { + nom::Err::Error(e) => e, + nom::Err::Failure(e) => e, + nom::Err::Incomplete(_) => Self::UnexpectedBufferEnd, + } + } +} diff --git a/app/rust/src/parser/from_bytes.rs b/app/rust/src/parser/from_bytes.rs new file mode 100644 index 0000000..95502e1 --- /dev/null +++ b/app/rust/src/parser/from_bytes.rs @@ -0,0 +1,37 @@ +use core::mem::MaybeUninit; + +use super::ParserError; + +///This trait defines an useful interface to parse +///objects from bytes. +///this gives different objects in a transaction +///a way to define their own deserilization implementation, allowing higher level objects to generalize the +///parsing of their inner types +pub trait FromBytes<'b>: Sized { + /// this method is avaliable for testing only, as the preferable + /// option is to save stack by passing the memory where the object should + /// store itself + #[cfg(test)] + fn from_bytes(input: &'b [u8]) -> Result<(&'b [u8], Self), nom::Err> { + let mut out = MaybeUninit::uninit(); + let rem = Self::from_bytes_into(input, &mut out)?; + unsafe { Ok((rem, out.assume_init())) } + } + + ///Main deserialization method + ///`input` the input data that contains the serialized form in bytes of this object. + ///`out` the memory where this object would be stored + /// + /// returns the remaining bytes on success + /// + /// `Safety` Dealing with uninitialize memory is undefine behavior + /// even in rust, so implementors should follow the rust documentation + /// for MaybeUninit and unsafe guidelines. + /// + /// It's a good idea to always put `#[inline(never)]` on top of this + /// function's implementation + fn from_bytes_into( + input: &'b [u8], + out: &mut MaybeUninit, + ) -> Result<&'b [u8], nom::Err>; +} diff --git a/app/rust/src/parser/memo.rs b/app/rust/src/parser/memo.rs new file mode 100644 index 0000000..934b47e --- /dev/null +++ b/app/rust/src/parser/memo.rs @@ -0,0 +1,36 @@ +use core::ptr::addr_of_mut; + +use arrayref::array_ref; +use nom::bytes::complete::take; + +use crate::{parser::constants::MEMO_SIZE, parser::FromBytes}; + +/// Memo field on a Note. Used to encode transaction IDs or other information +/// about the transaction. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +pub struct Memo(pub [u8; MEMO_SIZE]); + +impl<'a> FromBytes<'a> for Memo { + #[inline(never)] + fn from_bytes_into( + input: &'a [u8], + out: &mut core::mem::MaybeUninit, + ) -> Result<&'a [u8], nom::Err> { + let (rem, raw) = take(MEMO_SIZE)(input)?; + let bytes = array_ref!(raw, 0, MEMO_SIZE); + + let out = out.as_mut_ptr(); + + unsafe { + addr_of_mut!((*out).0).write(*bytes); + } + + Ok(rem) + } +} + +impl From<[u8; MEMO_SIZE]> for Memo { + fn from(value: [u8; MEMO_SIZE]) -> Self { + Memo(value) + } +} diff --git a/app/rust/src/parser/merkle_note.rs b/app/rust/src/parser/merkle_note.rs new file mode 100644 index 0000000..8ae6f3b --- /dev/null +++ b/app/rust/src/parser/merkle_note.rs @@ -0,0 +1,150 @@ +use core::ptr::addr_of_mut; +use group::{ + cofactor::{CofactorCurve, CofactorCurveAffine, CofactorGroup}, + prime::PrimeGroup, + Curve, Group, +}; + +use crate::{ + crypto::decrypt, + ironfish::public_address::PublicAddress, + parser::{ + ParserError, AFFINE_POINT_SIZE, ENCRYPTED_NOTE_SIZE, MAC_SIZE, NOTE_ENCRYPTION_KEY_SIZE, + }, +}; +use crate::{ + crypto::{calculate_key_for_encryption_keys, parse_affine_point, read_fr, read_scalar}, + ironfish::{ + errors::IronfishError, + view_keys::{shared_secret, OutgoingViewKey}, + }, + parser::FromBytes, +}; +use arrayref::array_ref; +use jubjub::AffinePoint; +use jubjub::{ExtendedPoint, Scalar, SubgroupPoint}; +use nom::bytes::complete::take; + +use super::{Note, ENCRYPTED_SHARED_KEY_SIZE}; + +#[derive(Clone, Debug)] +pub struct MerkleNote<'a> { + /// Randomized value commitment. Sometimes referred to as + /// `cv` in the literature. It's calculated by multiplying a value by a + /// random number. Commits this note to the value it contains + /// without revealing what that value is. + /// Use AffinePoint instead of ExtendedPoint + /// for simplicity and easy conversion from/to bytes + pub(crate) value_commitment: AffinePoint, + + /// The hash of the note, committing to it's internal state + // pub(crate) note_commitment: Scalar, + pub(crate) note_commitment: &'a [u8; 32], + + /// Public part of ephemeral diffie-hellman key-pair. See the discussion on + /// [`shared_secret`] to understand how this is used + // pub(crate) ephemeral_public_key: SubgroupPoint, + // We use AffinePoint because it is more compact and memory efficient + // we do not have the right API to parse bytes into a SubgroupPoint + pub(crate) ephemeral_public_key: AffinePoint, + + /// note as encrypted by the diffie hellman public key + /// we use this data to decrypt a Note as represented by: + /// https://github.com/iron-fish/ironfish/blob/master/ironfish-rust/src/note.rs#L88 + pub(crate) encrypted_note: &'a [u8; ENCRYPTED_NOTE_SIZE + MAC_SIZE], + + /// Keys used to encrypt the note. These are stored in encrypted format + /// using the spender's outgoing viewing key, and allow the spender to + /// decrypt it. The receiver (owner) doesn't need these, as they can decrypt + /// the note directly using their incoming viewing key. + pub(crate) note_encryption_keys: &'a [u8; NOTE_ENCRYPTION_KEY_SIZE], +} + +impl<'a> FromBytes<'a> for MerkleNote<'a> { + #[inline(never)] + fn from_bytes_into( + input: &'a [u8], + out: &mut core::mem::MaybeUninit, + ) -> Result<&'a [u8], nom::Err> { + let (rem, affine) = take(AFFINE_POINT_SIZE)(input)?; + let affine = affine + .try_into() + .map_err(|_| ParserError::ValueOutOfRange)?; + let value_commitment = parse_affine_point(affine)?; + + let (rem, raw_scalar) = take(32usize)(rem)?; + let note_commitment = array_ref!(raw_scalar, 0, 32); + + // parsing ephemeral pubkey which is a SubgroupPoint + let (rem, raw_scalar) = take(32usize)(rem)?; + let raw_scalar = array_ref!(raw_scalar, 0, 32); + + // ephemeral_public_key is a subgroupPoint + // however, we are read it as an extended point due to lack of support + // to compute it from bytes, ironfish uses a custom version of the jubjub + // crate that is incompatible with our target. + let ephemeral_public_key = parse_affine_point(raw_scalar)?; + + // encrypted_note + let (rem, raw_note) = take(ENCRYPTED_NOTE_SIZE + MAC_SIZE)(rem)?; + let encrypted_note = array_ref!(raw_note, 0, ENCRYPTED_NOTE_SIZE + MAC_SIZE); + + // note_encryption_keys + let (rem, encryption_keys) = take(NOTE_ENCRYPTION_KEY_SIZE)(rem)?; + let note_encryption_keys = array_ref!(encryption_keys, 0, NOTE_ENCRYPTION_KEY_SIZE); + + let out = out.as_mut_ptr(); + + unsafe { + addr_of_mut!((*out).value_commitment).write(value_commitment); + addr_of_mut!((*out).note_commitment).write(note_commitment); + addr_of_mut!((*out).ephemeral_public_key).write(ephemeral_public_key); + addr_of_mut!((*out).encrypted_note).write(encrypted_note); + addr_of_mut!((*out).note_encryption_keys).write(note_encryption_keys); + } + Ok(rem) + } +} + +impl<'a> MerkleNote<'a> { + #[inline(never)] + pub fn decrypt_note_for_spender( + &self, + spender_key: &OutgoingViewKey, + ) -> Result { + let encryption_key = calculate_key_for_encryption_keys( + spender_key, + &self.value_commitment, + &self.note_commitment, + &self.ephemeral_public_key.to_bytes(), + ); + + let note_encryption_keys: [u8; ENCRYPTED_SHARED_KEY_SIZE] = + decrypt(&encryption_key, self.note_encryption_keys)?; + + let public_address = PublicAddress::new(¬e_encryption_keys[..32].try_into().unwrap())?; + + let (rem, secret_key) = + read_fr(¬e_encryption_keys[32..]).map_err(|_| IronfishError::InvalidScalar)?; + let shared_key = shared_secret(&secret_key, &public_address.0, &self.ephemeral_public_key); + let note = + Note::from_spender_encrypted(public_address.0, &shared_key, &self.encrypted_note)?; + + // FIXME: Verify the node commitment + // note.verify_commitment(self.note_commitment)?; + + Ok(note) + } +} + +#[cfg(test)] +mod merkle_node_test { + use super::*; + const MERKLE_NOTE: &str = "280a055a0b05c6b0c93a457ba1509565473bcf6df318e72450a1b0e563f2f363412e27142ae4b554bd8c07c4587846c5ee3c36d04339a019462fabed7a4efc5cba51035633edb4cc6fa92c8586af3f9d7b5e22a3b7db7949e2864e82cebab2c218e529e8a5c0527348ec4c378f077af4d20fb886b0887640b8388e88735b9069afb2fe77c97185dc91fa53f19a5690c8ff4299c508be22c882ca3a21e844e0dd8ccd98b29379d0e08fa627265d14ee8b91770357509f1fb48eb87ba67bc717287d48685cff3b517691301d0f6186175eeb102eb4bbeee7225d68a166c3652615eb2fb077013d5e2da47e60a87663539eaef119584221f2a7158d3f3dc5c79da1e311c1ced8437bd506cab329bd7626ff97770597355b1b0d0a87328bb9f7f9da708c0af29d90f7845587df5b2bf08f0beb32d05cce5fbe7861ec8a7f5439216f1c6bf51c0fb8673d"; + + #[test] + fn parse_merkle_note() { + let bytes = hex::decode(MERKLE_NOTE).unwrap(); + MerkleNote::from_bytes(&bytes).unwrap(); + } +} diff --git a/js b/js index c3a55cb..f00c85c 160000 --- a/js +++ b/js @@ -1 +1 @@ -Subproject commit c3a55cb809f0fa5fc9206652e6094d573629fb32 +Subproject commit f00c85c9804cd8b6cfc82c20dbdfded1ff788e92