From eff907662ddbf36b0ba0c60b99c09d43952a9c7e Mon Sep 17 00:00:00 2001 From: the-real-adammork Date: Mon, 4 Apr 2022 03:40:52 -0700 Subject: [PATCH 1/8] copy-paste in rth changes --- libmobilecoin/Cargo.toml | 4 +- libmobilecoin/Makefile | 60 ++- libmobilecoin/include/keys.h | 15 + libmobilecoin/include/transaction.h | 288 ++++++++++- libmobilecoin/libmobilecoin_cbindgen.h | 270 +++++++++- libmobilecoin/src/common/buffer.rs | 40 ++ libmobilecoin/src/error.rs | 7 + libmobilecoin/src/keys.rs | 34 +- libmobilecoin/src/transaction.rs | 688 ++++++++++++++++++++++++- libmobilecoin/toolchain-config.env | 9 + 10 files changed, 1368 insertions(+), 47 deletions(-) create mode 100644 libmobilecoin/toolchain-config.env diff --git a/libmobilecoin/Cargo.toml b/libmobilecoin/Cargo.toml index 47f3a4ad25..c6e549261c 100644 --- a/libmobilecoin/Cargo.toml +++ b/libmobilecoin/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libmobilecoin" -version = "1.2.0-pre2" +version = "1.3.0-pre0" authors = ["MobileCoin"] edition = "2018" @@ -20,6 +20,8 @@ sha2 = "0.9.8" slip10_ed25519 = "0.1.3" tiny-bip39 = "0.8" zeroize = "1.5" +hex = "0.4.3" +generic-array = { version = "0.14", features = ["serde", "more_lengths"] } # Lock a specific cmake version that plays nicely with iOS. Note that 0.1.45 does not actually do that, # but there is an override to a specific commit of a currently-unreleased version in the root Cargo.toml. diff --git a/libmobilecoin/Makefile b/libmobilecoin/Makefile index 8f40f93b41..11995a3d9d 100644 --- a/libmobilecoin/Makefile +++ b/libmobilecoin/Makefile @@ -26,17 +26,14 @@ CARGO ?= cargo ######## Internal Variables ######## #################################### -### Environment Variables - -UNAME_S := $(shell uname -s) -ifeq ($(UNAME_S),Darwin) - export LIBCLANG_PATH = $(shell brew --prefix llvm)/lib -endif - ### Local Variables +DOT:= . +DASH:= - + CARGO_PACKAGE = libmobilecoin ARCHS_IOS = aarch64-apple-ios-sim aarch64-apple-ios-macabi aarch64-apple-ios x86_64-apple-ios-macabi x86_64-apple-ios x86_64-apple-darwin +TARGET_TOOLCHAINS = toolchain_aarch64-apple-ios-sim toolchain_aarch64-apple-ios-macabi toolchain_aarch64-apple-ios toolchain_x86_64-apple-ios-macabi toolchain_x86_64-apple-ios toolchain_x86_64-apple-darwin # add l_ prefix to build-targets that use the legacy build-process ALL_LEGACY_ARCHS_IOS = l_aarch64-apple-ios-sim l_aarch64-apple-ios-macabi l_aarch64-apple-ios l_x86_64-apple-ios-macabi l_x86_64-apple-ios l_x86_64-apple-darwin @@ -45,9 +42,10 @@ IOS_LIB = libmobilecoin.a IOS_LIB_STRIPPED = libmobilecoin_stripped.a IOS_C_HEADERS = include/* -RUST_COMMIT = 4e282795d -RUST_COMMIT_NAME = nightly-2021-08-01 -SWIFT_VERSION = 5.3.2 +RUST_BITCODE_COMMIT = $(shell source toolchain-config.env; echo $$RUST_BITCODE_COMMIT) +RUST_COMMIT = $(shell source toolchain-config.env; echo $$RUST_COMMIT) +RUST_COMMIT_NAME = $(shell source toolchain-config.env; echo $$RUST_COMMIT_NAME) +SWIFT_VERSION = $(shell source toolchain-config.env; echo $$SWIFT_VERSION) ### Helper Function @@ -58,13 +56,13 @@ define BINARY_copy endef # First argument $(1) is a build-target -define TOOLCHAIN_install +define TOOLCHAIN_install $(if \ $(shell rustup toolchain list | \ - grep "ios-$(1)-$(RUST_COMMIT_NAME)-swift-$(subst $(DOT),$(DASH),$(SWIFT_VERSION))" | \ - wc -l | \ - awk '{$$1=$$1;print}' | \ - grep '1'), \ + grep "ios-$(1)-$(RUST_COMMIT_NAME)-swift-$(subst $(DOT),$(DASH),$(SWIFT_VERSION))" | \ + wc -l | \ + awk '{$$1=$$1;print}' | \ + grep '1'), \ @echo "toolchain ios-$(1)-$(RUST_COMMIT_NAME)-swift-$(subst $(DOT),$(DASH),$(SWIFT_VERSION)) already installed", \ @echo "toolchain ios-$(1)-$(RUST_COMMIT_NAME)-swift-$(subst $(DOT),$(DASH),$(SWIFT_VERSION)) not installed"; \ $(info "Compiling & Installing Rust $(RUST_COMMIT_NAME) toolchain") \ @@ -72,16 +70,10 @@ define TOOLCHAIN_install $(info "from `rust` commit - $(RUST_COMMIT)") \ $(info "for build-target - $(1)") \ cd rust-bitcode && \ - ./build-install.sh -t $@ -l $(SWIFT_VERSION) -r $(RUST_COMMIT) -n $(RUST_COMMIT_NAME); \ + ./build-install.sh -t $(1) -l $(SWIFT_VERSION) -r $(RUST_COMMIT) -n $(RUST_COMMIT_NAME); \ ) endef -define RUSTBITCODE_init -endef - -DOT:= . -DASH:= - - #################################### ############## Targets ############# #################################### @@ -89,15 +81,24 @@ DASH:= - .PHONY: all all: setup ios +.PHONY: bitcode +bitcode: setup bitcode-init + +.PHONY: bitcode-init +bitcode-init: + cd rust-bitcode && ./build-install.sh -t $@ -l $(SWIFT_VERSION) -r $(RUST_COMMIT) -n $(RUST_COMMIT_NAME); + .PHONY: setup -setup: +setup: + $(info "Checking selected Xcode is version 12") + xcodebuild -version | grep 'Xcode 12' mkdir -p rust-bitcode cd rust-bitcode && \ git init && \ git remote add origin git@github.com:mobilecoinofficial/rust-bitcode; \ - git fetch --depth 1 origin a49da0265778fce5dd6521ba609b4c1d0a0959ad; \ + git fetch --depth 1 origin $(RUST_BITCODE_COMMIT); \ git checkout FETCH_HEAD; - + .PHONY: ios ios: out/ios/target @@ -112,15 +113,22 @@ else CARGO_BUILD_FLAGS += -Z unstable-options --profile ${CARGO_PROFILE} endif -LEGACY_CARGO_BUILD_FLAGS = $(CARGO_BUILD_FLAGS) +LEGACY_CARGO_BUILD_FLAGS = $(CARGO_BUILD_FLAGS) .PHONY: $(ARCHS_IOS) x86_64-apple-ios aarch64-apple-ios: CARGO_ENV_FLAGS += CFLAGS="-DPB_NO_PACKED_STRUCTS=1" x86_64-apple-ios aarch64-apple-ios: CARGO_ENV_FLAGS += CXXFLAGS="-DPB_NO_PACKED_STRUCTS=1" $(ARCHS_IOS): + mkdir -p out/ios $(call TOOLCHAIN_install,$@) $(CARGO_ENV_FLAGS) $(CARGO) +ios-$@-$(RUST_COMMIT_NAME)-swift-$(subst $(DOT),$(DASH),$(SWIFT_VERSION)) build --package $(CARGO_PACKAGE) --target $@ $(CARGO_BUILD_FLAGS) + $(call BINARY_copy,$@,target) + mkdir -p out/ios/include + cp $(IOS_C_HEADERS) out/ios/include +.PHONY: $(TARGET_TOOLCHAINS) +$(TARGET_TOOLCHAINS): + $(call TOOLCHAIN_install,$(subst toolchain_,,$@)) .PHONY: out/ios/target out/ios/target: $(ARCHS_IOS) diff --git a/libmobilecoin/include/keys.h b/libmobilecoin/include/keys.h index 88f5cd37dc..70515159fc 100644 --- a/libmobilecoin/include/keys.h +++ b/libmobilecoin/include/keys.h @@ -80,6 +80,21 @@ bool mc_account_key_get_public_address_fog_authority_sig( ) MC_ATTRIBUTE_NONNULL(1, 3); +/// # Preconditions +/// +/// * `public_address` - must be a valid `PublicAddress`. +/// * `out_short_address_hash` - length must be >= 16 bytes +/// +/// # Errors +/// +/// * `LibMcError::InvalidInput` +bool mc_account_key_get_short_address_hash( + const McPublicAddress* MC_NONNULL public_address, + McMutableBuffer* MC_NONNULL out_short_address_hash, + McError* MC_NULLABLE * MC_NULLABLE out_error +) +MC_ATTRIBUTE_NONNULL(1, 2, 3); + #ifdef __cplusplus } #endif diff --git a/libmobilecoin/include/transaction.h b/libmobilecoin/include/transaction.h index 46c48141e6..473864582f 100644 --- a/libmobilecoin/include/transaction.h +++ b/libmobilecoin/include/transaction.h @@ -21,6 +21,7 @@ typedef struct { typedef struct _McTransactionBuilderRing McTransactionBuilderRing; typedef struct _McTransactionBuilder McTransactionBuilder; +typedef struct _McTxOutMemoBuilder McTxOutMemoBuilder; /* ==== TxOut ==== */ @@ -169,7 +170,9 @@ MC_ATTRIBUTE_NONNULL(1, 2, 3); McTransactionBuilder* MC_NULLABLE mc_transaction_builder_create( uint64_t fee, uint64_t tombstone_block, - const McFogResolver* MC_NULLABLE fog_resolver + const McFogResolver* MC_NULLABLE fog_resolver, + McTxOutMemoBuilder* MC_NONNULL memo_builder, + uint32_t block_version ); void mc_transaction_builder_free( @@ -217,6 +220,27 @@ McData* MC_NULLABLE mc_transaction_builder_add_output( ) MC_ATTRIBUTE_NONNULL(1, 3, 6); +/// # Preconditions +/// +/// * `account_kay` - must be a valid account key, default change address computed from account key +/// * `transaction_builder` - must not have been previously consumed by a call +/// to `build`. +/// * `out_tx_out_confirmation_number` - length must be >= 32. +/// +/// # Errors +/// +/// * `LibMcError::AttestationVerification` +/// * `LibMcError::InvalidInput` +McData* MC_NULLABLE mc_transaction_builder_add_change_output( + const McAccountKey* MC_NONNULL account_key, + McTransactionBuilder* MC_NONNULL transaction_builder, + uint64_t amount, + McRngCallback* MC_NULLABLE rng_callback, + McMutableBuffer* MC_NONNULL out_tx_out_confirmation_number, + McError* MC_NULLABLE * MC_NULLABLE out_error +) +MC_ATTRIBUTE_NONNULL(1, 2, 4, 6); + /// # Preconditions /// /// * `transaction_builder` - must not have been previously consumed by a call to `build`. @@ -253,6 +277,268 @@ McData* MC_NULLABLE mc_transaction_builder_build( ) MC_ATTRIBUTE_NONNULL(1); + +/// # Preconditions +/// +/// * `account_key` - must be a valid `AccountKey` with `fog_info`. +McTxOutMemoBuilder* MC_NULLABLE mc_memo_builder_sender_and_destination_create( + const McAccountKey* MC_NONNULL account_key) +MC_ATTRIBUTE_NONNULL(1); + +/// # Preconditions +/// +/// * `account_key` - must be a valid `AccountKey` with `fog_info`. +McTxOutMemoBuilder* MC_NULLABLE mc_memo_builder_sender_payment_request_and_destination_create( + uint64_t payment_request_id, + const McAccountKey* MC_NONNULL account_key +) +MC_ATTRIBUTE_NONNULL(2); + +McTxOutMemoBuilder* MC_NULLABLE mc_memo_builder_default_create(); + + +void mc_memo_builder_free( + McTxOutMemoBuilder* MC_NULLABLE memo_builder +); + + +/* ==== SenderMemo ==== */ + +/// # Preconditions +/// +/// * `sender_memo_data` - must be 64 bytes +/// * `sender_public_address` - must be a valid `PublicAddress`. +/// * `receiving_subaddress_view_private_key` - must be a valid +/// 32-byte Ristretto-format scalar. +/// * `tx_out_public_key` - must be a valid 32-byte Ristretto-format scalar. +/// +/// # Errors +/// +/// * `LibMcError::InvalidInput` +bool mc_memo_sender_memo_is_valid( + const McBuffer* MC_NONNULL sender_memo_data, + const McPublicAddress* MC_NONNULL sender_public_address, + const McBuffer* MC_NONNULL receiving_subaddress_view_private_key, + const McBuffer* MC_NONNULL tx_out_public_key, + bool* MC_NONNULL out_valid, + McError* MC_NULLABLE * MC_NULLABLE out_error +) +MC_ATTRIBUTE_NONNULL(1, 2, 3, 4, 5); + +/// # Preconditions +/// +/// * `sender_account_key` - must be a valid account key +/// * `recipient_subaddress_view_public_key` - must be a valid +/// 32-byte Ristretto-format scalar. +/// * `tx_out_public_key` - must be a valid 32-byte Ristretto-format scalar. +/// * `out_memo_data` - length must be >= 64. +/// +/// # Errors +/// +/// * `LibMcError::InvalidInput` +bool mc_memo_sender_memo_create( + const McAccountKey* MC_NONNULL sender_account_key, + const McBuffer* MC_NONNULL recipient_subaddress_view_public_key, + const McBuffer* MC_NONNULL tx_out_public_key, + McMutableBuffer* MC_NONNULL out_memo_data, + McError* MC_NULLABLE * MC_NULLABLE out_error +) +MC_ATTRIBUTE_NONNULL(1, 2, 3, 4); + +/// # Preconditions +/// +/// * `sender_memo_data` - must be 64 bytes +/// * `out_short_address_hash` - length must be >= 16 bytes +/// +/// # Errors +/// +/// * `LibMcError::InvalidInput` +bool mc_memo_sender_memo_get_address_hash( + const McBuffer* MC_NONNULL sender_memo_data, + McMutableBuffer* MC_NONNULL out_short_address_hash, + McError* MC_NULLABLE * MC_NULLABLE out_error +) +MC_ATTRIBUTE_NONNULL(1, 2); + + +/* ==== DestinationMemo ==== */ + +/// # Preconditions +/// +/// * `destination_public_address` - must be a valid 32-byte +/// Ristretto-format scalar. +/// * `number_of_recipients` - must be > 0 +/// * `out_memo_data` - length must be >= 64. +/// +/// # Errors +/// +/// * `LibMcError::InvalidInput` +bool mc_memo_destination_memo_create( + const McPublicAddress* MC_NONNULL destination_public_address, + uint8_t number_of_recipients, + uint64_t fee, + uint64_t total_outlay, + McMutableBuffer* MC_NONNULL out_memo_data, + McError* MC_NULLABLE * MC_NULLABLE out_error +) +MC_ATTRIBUTE_NONNULL(1, 2, 3, 4); + +/// # Preconditions +/// +/// * `destination_memo_data` - must be 64 bytes +/// * `out_short_address_hash` - length must be >= 16 bytes +/// +/// # Errors +/// +/// * `LibMcError::InvalidInput` +bool mc_memo_destination_memo_get_address_hash( + const McBuffer* MC_NONNULL destination_memo_data, + McMutableBuffer* MC_NONNULL out_short_address_hash, + McError* MC_NULLABLE * MC_NULLABLE out_error +) +MC_ATTRIBUTE_NONNULL(1, 2); + +/// # Preconditions +/// +/// * `destination_memo_data` - must be 64 bytes +/// +/// # Errors +/// +/// * `LibMcError::InvalidInput` +bool mc_memo_destination_memo_get_number_of_recipients( + const McBuffer* MC_NONNULL destination_memo_data, + uint8_t* MC_NONNULL out_number_of_recipients, + McError* MC_NULLABLE * MC_NULLABLE out_error +) +MC_ATTRIBUTE_NONNULL(1, 2); + +/// # Preconditions +/// +/// * `destination_memo_data` - must be 64 bytes +/// +/// # Errors +/// +/// * `LibMcError::InvalidInput` +bool mc_memo_destination_memo_get_fee( + const McBuffer* MC_NONNULL destination_memo_data, + uint64_t* MC_NONNULL out_fee, + McError* MC_NULLABLE * MC_NULLABLE out_error +) +MC_ATTRIBUTE_NONNULL(1, 2); + +/// # Preconditions +/// +/// * `destination_memo_data` - must be 64 bytes +/// +/// # Errors +/// +/// * `LibMcError::InvalidInput` +bool mc_memo_destination_memo_get_total_outlay( + const McBuffer* MC_NONNULL destination_memo_data, + uint64_t* MC_NONNULL out_total_outlay, + McError* MC_NULLABLE * MC_NULLABLE out_error +) +MC_ATTRIBUTE_NONNULL(1, 2); + + +/* ==== SenderWithPaymentRequestMemo ==== */ + + +/// # Preconditions +/// +/// * `sender_with_payment_request_memo_data` - must be 64 bytes +/// * `sender_public_address` - must be a valid `PublicAddress`. +/// * `receiving_subaddress_view_private_key` - must be a valid +/// 32-byte Ristretto-format scalar. +/// * `tx_out_public_key` - must be a valid 32-byte Ristretto-format scalar. +/// +/// # Errors +/// +/// * `LibMcError::InvalidInput` +bool mc_memo_sender_with_payment_request_memo_is_valid( + const McBuffer* MC_NONNULL sender_with_payment_request_memo_data, + const McPublicAddress* MC_NONNULL sender_public_address, + const McBuffer* MC_NONNULL receiving_subaddress_view_private_key, + const McBuffer* MC_NONNULL tx_out_public_key, + bool* MC_NONNULL out_valid, + McError* MC_NULLABLE * MC_NULLABLE out_error +) +MC_ATTRIBUTE_NONNULL(1, 2, 3, 4, 5); + +/// # Preconditions +/// +/// * `sender_account_key` - must be a valid account key +/// * `recipient_subaddress_view_public_key` - must be a valid +/// 32-byte Ristretto-format scalar. +/// * `tx_out_public_key` - must be a valid 32-byte Ristretto-format scalar. +/// * `out_memo_data` - length must be >= 64. +/// +/// # Errors +/// +/// * `LibMcError::InvalidInput` +bool mc_memo_sender_with_payment_request_memo_create( + const McAccountKey* MC_NONNULL sender_account_key, + const McBuffer* MC_NONNULL recipient_subaddress_view_public_key, + const McBuffer* MC_NONNULL tx_out_public_key, + uint64_t payment_request_id, + McMutableBuffer* MC_NONNULL out_memo_data, + McError* MC_NULLABLE * MC_NULLABLE out_error +) +MC_ATTRIBUTE_NONNULL(1, 2, 3, 5); + +/// # Preconditions +/// +/// * `sender_with_payment_request_memo_data` - must be 64 bytes +/// * `out_short_address_hash` - length must be >= 16 bytes +/// +/// # Errors +/// +/// * `LibMcError::InvalidInput` +bool mc_memo_sender_with_payment_request_memo_get_address_hash( + const McBuffer* MC_NONNULL sender_with_payment_request_memo_data, + McMutableBuffer* MC_NONNULL out_short_address_hash, + McError* MC_NULLABLE * MC_NULLABLE out_error +) +MC_ATTRIBUTE_NONNULL(1, 2); + +/// # Preconditions +/// +/// * `sender_with_payment_request_memo_data` - must be 64 bytes +/// +/// # Errors +/// +/// * `LibMcError::InvalidInput` +bool mc_memo_sender_with_payment_request_memo_get_payment_request_id( + const McBuffer* MC_NONNULL sender_with_payment_request_memo_data, + uint64_t* MC_NONNULL out_payment_request_id, + McError* MC_NULLABLE * MC_NULLABLE out_error +) +MC_ATTRIBUTE_NONNULL(1, 2); + + +/* ==== Decrypt Memo Payload ==== */ + + +/// # Preconditions +/// +/// * `encrypted_memo` - must be 66 bytes +/// * `tx_out_public_key` - must be a valid 32-byte Ristretto-format scalar. +/// * `account_key` - must be a valid account key +/// * `out_memo_payload` - length must be >= 16 bytes +/// +/// # Errors +/// +/// * `LibMcError::InvalidInput` +bool mc_memo_decrypt_e_memo_payload( + const McBuffer* MC_NONNULL encrypted_memo, + const McBuffer* MC_NONNULL tx_out_public_key, + const McAccountKey* MC_NONNULL account_key, + McMutableBuffer* MC_NONNULL out_memo_data, + McError* MC_NULLABLE * MC_NULLABLE out_error +) +MC_ATTRIBUTE_NONNULL(1, 2, 3, 4); + + #ifdef __cplusplus } #endif diff --git a/libmobilecoin/libmobilecoin_cbindgen.h b/libmobilecoin/libmobilecoin_cbindgen.h index a3988ab9f6..f4128365a1 100644 --- a/libmobilecoin/libmobilecoin_cbindgen.h +++ b/libmobilecoin/libmobilecoin_cbindgen.h @@ -32,6 +32,8 @@ typedef struct McFogResolver McFogResolver; typedef struct McTransactionBuilderRing McTransactionBuilderRing; +typedef struct McTxOutMemoBuilder McTxOutMemoBuilder; + typedef struct Option_TransactionBuilder_FogResolver Option_TransactionBuilder_FogResolver; typedef struct Vec_u8 Vec_u8; @@ -579,6 +581,20 @@ bool mc_account_key_get_public_address_fog_authority_sig(FfiRefPtr uint64_t subaddress_index, FfiMutPtr out_fog_authority_sig); +/** + * # Preconditions + * + * * `public_address` - must be a valid `PublicAddress`. + * * `out_short_address_hash` - length must be >= 16 bytes + * + * # Errors + * + * * `LibMcError::InvalidInput` + */ +bool mc_account_key_get_short_address_hash(FfiRefPtr public_address, + FfiMutPtr out_short_address_hash, + FfiOptMutPtr> out_error); + /** * # Preconditions * @@ -723,7 +739,9 @@ bool mc_transaction_builder_ring_add_element(FfiMutPtr FfiOptOwnedPtr mc_transaction_builder_create(uint64_t fee, uint64_t tombstone_block, - FfiOptRefPtr fog_resolver); + FfiOptRefPtr fog_resolver, + FfiMutPtr memo_builder, + uint32_t block_version); void mc_transaction_builder_free(FfiOptOwnedPtr transaction_builder); @@ -769,6 +787,27 @@ FfiOptOwnedPtr mc_transaction_builder_add_output(FfiMutPtr out_tx_out_confirmation_number, FfiOptMutPtr> out_error); +/** + * # Preconditions + * + * * `account_key` - must be a valid account key, default change address + * computed from account key + * * `transaction_builder` - must not have been previously consumed by a call + * to `build`. + * * `out_tx_out_confirmation_number` - length must be >= 32. + * + * # Errors + * + * * `LibMcError::AttestationVerification` + * * `LibMcError::InvalidInput` + */ +FfiOptOwnedPtr mc_transaction_builder_add_change_output(FfiRefPtr account_key, + FfiMutPtr transaction_builder, + uint64_t amount, + FfiOptMutPtr rng_callback, + FfiMutPtr out_tx_out_confirmation_number, + FfiOptMutPtr> out_error); + /** * # Preconditions * @@ -804,3 +843,232 @@ FfiOptOwnedPtr mc_transaction_builder_add_output_with_fog_hint_address(F FfiOptOwnedPtr mc_transaction_builder_build(FfiMutPtr transaction_builder, FfiOptMutPtr rng_callback, FfiOptMutPtr> out_error); + +/** + * # Preconditions + * + * * `account_key` - must be a valid `AccountKey` with `fog_info`. + */ +FfiOptOwnedPtr mc_memo_builder_sender_and_destination_create(FfiRefPtr account_key); + +/** + * # Preconditions + * + * * `account_key` - must be a valid `AccountKey` with `fog_info`. + */ +FfiOptOwnedPtr mc_memo_builder_sender_payment_request_and_destination_create(uint64_t payment_request_id, + FfiRefPtr account_key); + +FfiOptOwnedPtr mc_memo_builder_default_create(void); + +void mc_memo_builder_free(FfiOptOwnedPtr memo_builder); + +/** + * # Preconditions + * + * * `sender_memo_data` - must be 64 bytes + * * `sender_public_address` - must be a valid `PublicAddress`. + * * `receiving_subaddress_view_private_key` - must be a valid + * 32-byte Ristretto-format scalar. + * * `tx_out_public_key` - must be a valid 32-byte Ristretto-format scalar. + * + * # Errors + * + * * `LibMcError::InvalidInput` + */ +bool mc_memo_sender_memo_is_valid(FfiRefPtr sender_memo_data, + FfiRefPtr sender_public_address, + FfiRefPtr receiving_subaddress_view_private_key, + FfiRefPtr tx_out_public_key, + FfiMutPtr out_valid, + FfiOptMutPtr> out_error); + +/** + * # Preconditions + * + * * `sender_account_key` - must be a valid account key + * * `recipient_subaddress_view_public_key` - must be a valid + * 32-byte Ristretto-format scalar. + * * `tx_out_public_key` - must be a valid 32-byte Ristretto-format scalar. + * * `out_memo_data` - length must be >= 64. + * + * # Errors + * + * * `LibMcError::InvalidInput` + */ +bool mc_memo_sender_memo_create(FfiRefPtr sender_account_key, + FfiRefPtr recipient_subaddress_view_public_key, + FfiRefPtr tx_out_public_key, + FfiMutPtr out_memo_data, + FfiOptMutPtr> out_error); + +/** + * # Preconditions + * + * * `sender_memo_data` - must be 64 bytes + * * `out_short_address_hash` - length must be >= 16 bytes + * + * # Errors + * + * * `LibMcError::InvalidInput` + */ +bool mc_memo_sender_memo_get_address_hash(FfiRefPtr sender_memo_data, + FfiMutPtr out_short_address_hash, + FfiOptMutPtr> out_error); + +/** + * # Preconditions + * + * * `destination_public_address` - must be a valid 32-byte + * Ristretto-format scalar. + * * `number_of_recipients` - must be > 0 + * * `out_memo_data` - length must be >= 64. + * + * # Errors + * + * * `LibMcError::InvalidInput` + */ +bool mc_memo_destination_memo_create(FfiRefPtr destination_public_address, + uint8_t number_of_recipients, + uint64_t fee, + uint64_t total_outlay, + FfiMutPtr out_memo_data, + FfiOptMutPtr> out_error); + +/** + * # Preconditions + * + * * `destination_memo_data` - must be 64 bytes + * * `out_short_address_hash` - length must be >= 16 bytes + * + * # Errors + * + * * `LibMcError::InvalidInput` + */ +bool mc_memo_destination_memo_get_address_hash(FfiRefPtr destination_memo_data, + FfiMutPtr out_short_address_hash, + FfiOptMutPtr> out_error); + +/** + * # Preconditions + * + * * `destination_memo_data` - must be 64 bytes + * + * # Errors + * + * * `LibMcError::InvalidInput` + */ +bool mc_memo_destination_memo_get_number_of_recipients(FfiRefPtr destination_memo_data, + FfiMutPtr out_number_of_recipients, + FfiOptMutPtr> out_error); + +/** + * # Preconditions + * + * * `destination_memo_data` - must be 64 bytes + * + * # Errors + * + * * `LibMcError::InvalidInput` + */ +bool mc_memo_destination_memo_get_fee(FfiRefPtr destination_memo_data, + FfiMutPtr out_fee, + FfiOptMutPtr> out_error); + +/** + * # Preconditions + * + * * `destination_memo_data` - must be 64 bytes + * + * # Errors + * + * * `LibMcError::InvalidInput` + */ +bool mc_memo_destination_memo_get_total_outlay(FfiRefPtr destination_memo_data, + FfiMutPtr out_total_outlay, + FfiOptMutPtr> out_error); + +/** + * # Preconditions + * + * * `sender_with_payment_request_memo_data` - must be 64 bytes + * * `sender_public_address` - must be a valid `PublicAddress`. + * * `receiving_subaddress_view_private_key` - must be a valid + * 32-byte Ristretto-format scalar. + * * `tx_out_public_key` - must be a valid 32-byte Ristretto-format scalar. + * + * # Errors + * + * * `LibMcError::InvalidInput` + */ +bool mc_memo_sender_with_payment_request_memo_is_valid(FfiRefPtr sender_with_payment_request_memo_data, + FfiRefPtr sender_public_address, + FfiRefPtr receiving_subaddress_view_private_key, + FfiRefPtr tx_out_public_key, + FfiMutPtr out_valid, + FfiOptMutPtr> out_error); + +/** + * # Preconditions + * + * * `sender_account_key` - must be a valid account key + * * `recipient_subaddress_view_public_key` - must be a valid + * 32-byte Ristretto-format scalar. + * * `tx_out_public_key` - must be a valid 32-byte Ristretto-format scalar. + * * `out_memo_data` - length must be >= 64. + * + * # Errors + * + * * `LibMcError::InvalidInput` + */ +bool mc_memo_sender_with_payment_request_memo_create(FfiRefPtr sender_account_key, + FfiRefPtr recipient_subaddress_view_public_key, + FfiRefPtr tx_out_public_key, + uint64_t payment_request_id, + FfiMutPtr out_memo_data, + FfiOptMutPtr> out_error); + +/** + * # Preconditions + * + * * `sender_with_payment_request_memo_data` - must be 64 bytes + * * `out_short_address_hash` - length must be >= 16 bytes + * + * # Errors + * + * * `LibMcError::InvalidInput` + */ +bool mc_memo_sender_with_payment_request_memo_get_address_hash(FfiRefPtr sender_with_payment_request_memo_data, + FfiMutPtr out_short_address_hash, + FfiOptMutPtr> out_error); + +/** + * # Preconditions + * + * * `sender_with_payment_request_memo_data` - must be 64 bytes + * + * # Errors + * + * * `LibMcError::InvalidInput` + */ +bool mc_memo_sender_with_payment_request_memo_get_payment_request_id(FfiRefPtr sender_with_payment_request_memo_data, + FfiMutPtr out_payment_request_id, + FfiOptMutPtr> out_error); + +/** + * # Preconditions + * + * * `encrypted_memo` - must be 66 bytes + * * `tx_out_public_key` - must be a valid 32-byte Ristretto-format scalar. + * * `account_key` - must be a valid account key + * * `out_memo_payload` - length must be >= 16 bytes + * + * # Errors + * + * * `LibMcError::InvalidInput` + */ +bool mc_memo_decrypt_e_memo_payload(FfiRefPtr encrypted_memo, + FfiRefPtr tx_out_public_key, + FfiRefPtr account_key, + FfiMutPtr out_memo_payload, + FfiOptMutPtr> out_error); diff --git a/libmobilecoin/src/common/buffer.rs b/libmobilecoin/src/common/buffer.rs index 20d7393699..2bcb3d6c3c 100644 --- a/libmobilecoin/src/common/buffer.rs +++ b/libmobilecoin/src/common/buffer.rs @@ -198,6 +198,46 @@ impl<'a> TryFromFfi<&McBuffer<'a>> for [u8; 32] { } } +impl<'a> TryFromFfi<&McBuffer<'a>> for &'a [u8; 64] { + type Error = LibMcError; + + #[inline] + fn try_from_ffi(src: &McBuffer<'a>) -> Result { + let src = src.as_slice_of_len(64)?; + // SAFETY: ok to unwrap because we just checked length + Ok(<&[u8; 64]>::try_from(src).unwrap()) + } +} + +impl<'a> TryFromFfi<&McBuffer<'a>> for [u8; 64] { + type Error = LibMcError; + + #[inline] + fn try_from_ffi(src: &McBuffer<'a>) -> Result { + Ok(*<&'a [u8; 64]>::try_from_ffi(src)?) + } +} + +impl<'a> TryFromFfi<&McBuffer<'a>> for &'a [u8; 66] { + type Error = LibMcError; + + #[inline] + fn try_from_ffi(src: &McBuffer<'a>) -> Result { + let src = src.as_slice_of_len(66)?; + // SAFETY: ok to unwrap because we just checked length + Ok(<&[u8; 66]>::try_from(src).unwrap()) + } +} + +impl<'a> TryFromFfi<&McBuffer<'a>> for [u8; 66] { + type Error = LibMcError; + + #[inline] + fn try_from_ffi(src: &McBuffer<'a>) -> Result { + Ok(*<&'a [u8; 66]>::try_from_ffi(src)?) + } +} + impl<'a> FfiTryFrom for ssize_t { type Error = LibMcError; diff --git a/libmobilecoin/src/error.rs b/libmobilecoin/src/error.rs index cd1377eb3f..ce0e451ecd 100644 --- a/libmobilecoin/src/error.rs +++ b/libmobilecoin/src/error.rs @@ -6,6 +6,7 @@ use mc_api::display::Error as ApiDisplayError; use mc_attest_ake::Error as AttestAkeError; use mc_attest_verifier::Error as VerifierError; use mc_crypto_box::{AeadError, Error as CryptoBoxError}; +use mc_crypto_keys::KeyError; use mc_crypto_noise::CipherError; use mc_fog_kex_rng::Error as FogKexRngError; use mc_fog_report_validation::{ingest_report::Error as IngestReportError, FogPubkeyError}; @@ -111,6 +112,12 @@ impl From for LibMcError { } } +impl From for LibMcError { + fn from(err: KeyError) -> Self { + LibMcError::TransactionCrypto(format!("{:?}", err)) + } +} + impl From for LibMcError { fn from(err: ApiDisplayError) -> Self { LibMcError::InvalidInput(format!("{:?}", err)) diff --git a/libmobilecoin/src/keys.rs b/libmobilecoin/src/keys.rs index f6156ab9f5..c677e4a4c3 100644 --- a/libmobilecoin/src/keys.rs +++ b/libmobilecoin/src/keys.rs @@ -1,7 +1,7 @@ // Copyright 2018-2021 The MobileCoin Foundation use crate::{common::*, LibMcError}; -use mc_account_keys::{AccountKey, PublicAddress}; +use mc_account_keys::{AccountKey, PublicAddress, ShortAddressHash}; use mc_crypto_keys::{ReprBytes, RistrettoPrivate, RistrettoPublic}; use mc_util_ffi::*; @@ -150,6 +150,38 @@ pub extern "C" fn mc_account_key_get_public_address_fog_authority_sig( }) } +/* ==== TxOutMemoBuilder ==== */ + +/// # Preconditions +/// +/// * `public_address` - must be a valid `PublicAddress`. +/// * `out_short_address_hash` - length must be >= 16 bytes +/// +/// # Errors +/// +/// * `LibMcError::InvalidInput` +#[no_mangle] +pub extern "C" fn mc_account_key_get_short_address_hash( + public_address: FfiRefPtr, + out_short_address_hash: FfiMutPtr, + out_error: FfiOptMutPtr>, +) -> bool { + ffi_boundary_with_error(out_error, || { + let public_address = PublicAddress::try_from_ffi(&public_address)?; + + let short_address_hash: ShortAddressHash = ShortAddressHash::from(&public_address); + let hash_data: [u8; 16] = short_address_hash.into(); + + let out_short_address_hash = out_short_address_hash + .into_mut() + .as_slice_mut_of_len(core::mem::size_of_val(&hash_data)) + .expect("ShortAddressHash length is insufficient"); + + out_short_address_hash.copy_from_slice(&hash_data); + Ok(()) + }) +} + /* ==== PublicAddress ==== */ #[repr(C)] diff --git a/libmobilecoin/src/transaction.rs b/libmobilecoin/src/transaction.rs index c4c961a0ff..dcb6afc037 100644 --- a/libmobilecoin/src/transaction.rs +++ b/libmobilecoin/src/transaction.rs @@ -1,19 +1,32 @@ // Copyright (c) 2018-2021 The MobileCoin Foundation +// -use crate::{common::*, fog::McFogResolver, keys::McPublicAddress, LibMcError}; +use crate::{ + common::*, + fog::McFogResolver, + keys::{McAccountKey, McPublicAddress}, + LibMcError, +}; use core::convert::TryFrom; use crc::Crc; -use mc_account_keys::PublicAddress; -use mc_crypto_keys::{ReprBytes, RistrettoPrivate, RistrettoPublic}; +use generic_array::{typenum::U66, GenericArray}; +use mc_account_keys::{AccountKey, PublicAddress, ShortAddressHash}; +use mc_crypto_keys::{CompressedRistrettoPublic, ReprBytes, RistrettoPrivate, RistrettoPublic}; use mc_fog_report_validation::FogResolver; use mc_transaction_core::{ get_tx_out_shared_secret, get_value_mask, onetime_keys::{recover_onetime_private_key, recover_public_subaddress_spend_key}, ring_signature::KeyImage, tx::{TxOut, TxOutConfirmationNumber, TxOutMembershipProof}, - Amount, BlockVersion, CompressedCommitment, + Amount, BlockVersion, CompressedCommitment, EncryptedMemo, +}; + +use mc_transaction_std::{ + AuthenticatedSenderMemo, AuthenticatedSenderWithPaymentRequestIdMemo, ChangeDestination, + DestinationMemo, InputCredentials, MemoBuilder, MemoPayload, RTHMemoBuilder, + SenderMemoCredential, TransactionBuilder, }; -use mc_transaction_std::{InputCredentials, RTHMemoBuilder, TransactionBuilder}; + use mc_util_ffi::*; /* ==== TxOut ==== */ @@ -24,6 +37,9 @@ pub struct McTxOutAmount { masked_value: u64, } +pub type McTxOutMemoBuilder = Option>; +impl_into_ffi!(Option>); + /// # Preconditions /// /// * `view_private_key` - must be a valid 32-byte Ristretto-format scalar. @@ -330,6 +346,8 @@ pub extern "C" fn mc_transaction_builder_create( fee: u64, tombstone_block: u64, fog_resolver: FfiOptRefPtr, + memo_builder: FfiMutPtr, + block_version: u32, ) -> FfiOptOwnedPtr { ffi_boundary(|| { let fog_resolver = @@ -342,19 +360,17 @@ pub extern "C" fn mc_transaction_builder_create( FogResolver::new(fog_resolver.0.clone(), &fog_resolver.1) .expect("FogResolver could not be constructed from the provided materials") }); - // FIXME: block version should be a parameter, it should be the latest - // version that fog ledger told us about, or that we got from ledger-db - let block_version = BlockVersion::ZERO; - // Note: RTHMemoBuilder can be selected here, but we will only actually - // write memos if block_version is large enough that memos are supported. - // If block version is < 2, then transaction builder will filter out memos. - let mut memo_builder = RTHMemoBuilder::default(); - // FIXME: we need to pass the source account key to build sender memo - // credentials memo_builder.set_sender_credential(SenderMemoCredential:: - // from(source_account_key)); - memo_builder.enable_destination_memo(); + + let block_version = BlockVersion::try_from(block_version).unwrap(); + + let memo_builder_box = memo_builder + .into_mut() + .take() + .expect("McTxOutMemoBuilder has already been used to build a Tx"); + let mut transaction_builder = - TransactionBuilder::new(block_version, fog_resolver, memo_builder); + TransactionBuilder::new_with_box(block_version, fog_resolver, memo_builder_box); + transaction_builder .set_fee(fee) .expect("failure not expected"); @@ -478,6 +494,49 @@ pub extern "C" fn mc_transaction_builder_add_output( }) } +/// # Preconditions +/// +/// * `account_key` - must be a valid account key, default change address +/// computed from account key +/// * `transaction_builder` - must not have been previously consumed by a call +/// to `build`. +/// * `out_tx_out_confirmation_number` - length must be >= 32. +/// +/// # Errors +/// +/// * `LibMcError::AttestationVerification` +/// * `LibMcError::InvalidInput` +#[no_mangle] +pub extern "C" fn mc_transaction_builder_add_change_output( + account_key: FfiRefPtr, + transaction_builder: FfiMutPtr, + amount: u64, + rng_callback: FfiOptMutPtr, + out_tx_out_confirmation_number: FfiMutPtr, + out_error: FfiOptMutPtr>, +) -> FfiOptOwnedPtr { + ffi_boundary_with_error(out_error, || { + let account_key_obj = + AccountKey::try_from_ffi(&account_key).expect("account_key is invalid"); + let transaction_builder = transaction_builder + .into_mut() + .as_mut() + .expect("McTransactionBuilder instance has already been used to build a Tx"); + let change_destination = ChangeDestination::from(&account_key_obj); + let mut rng = SdkRng::from_ffi(rng_callback); + let out_tx_out_confirmation_number = out_tx_out_confirmation_number + .into_mut() + .as_slice_mut_of_len(TxOutConfirmationNumber::size()) + .expect("out_tx_out_confirmation_number length is insufficient"); + + let (tx_out, confirmation) = + transaction_builder.add_change_output(amount, &change_destination, &mut rng)?; + + out_tx_out_confirmation_number.copy_from_slice(confirmation.as_ref()); + Ok(mc_util_serial::encode(&tx_out)) + }) +} + /// # Preconditions /// /// * `transaction_builder` - must not have been previously consumed by a call @@ -535,6 +594,576 @@ pub extern "C" fn mc_transaction_builder_build( }) } +/* ==== TxOutMemoBuilder ==== */ + +/// # Preconditions +/// +/// * `account_key` - must be a valid `AccountKey` with `fog_info`. +#[no_mangle] +pub extern "C" fn mc_memo_builder_sender_and_destination_create( + account_key: FfiRefPtr, +) -> FfiOptOwnedPtr { + ffi_boundary(|| { + let account_key = AccountKey::try_from_ffi(&account_key).expect("account_key is invalid"); + let mut rth_memo_builder: RTHMemoBuilder = RTHMemoBuilder::default(); + rth_memo_builder.set_sender_credential(SenderMemoCredential::from(&account_key)); + rth_memo_builder.enable_destination_memo(); + + let memo_builder_box: Box = Box::new(rth_memo_builder); + + Some(memo_builder_box) + }) +} + +/// # Preconditions +/// +/// * `account_key` - must be a valid `AccountKey` with `fog_info`. +#[no_mangle] +pub extern "C" fn mc_memo_builder_sender_payment_request_and_destination_create( + payment_request_id: u64, + account_key: FfiRefPtr, +) -> FfiOptOwnedPtr { + ffi_boundary(|| { + let account_key = AccountKey::try_from_ffi(&account_key).expect("account_key is invalid"); + let mut rth_memo_builder: RTHMemoBuilder = RTHMemoBuilder::default(); + rth_memo_builder.set_sender_credential(SenderMemoCredential::from(&account_key)); + rth_memo_builder.set_payment_request_id(payment_request_id); + rth_memo_builder.enable_destination_memo(); + + let memo_builder_box: Box = Box::new(rth_memo_builder); + + Some(memo_builder_box) + }) +} + +#[no_mangle] +pub extern "C" fn mc_memo_builder_default_create() -> FfiOptOwnedPtr { + ffi_boundary(|| { + let memo_builder_box: Box = + Box::new(RTHMemoBuilder::default()); + Some(memo_builder_box) + }) +} + +#[no_mangle] +pub extern "C" fn mc_memo_builder_free(memo_builder: FfiOptOwnedPtr) { + ffi_boundary(|| { + let _ = memo_builder; + }) +} + +/* ==== SenderMemo ==== */ + +/// # Preconditions +/// +/// * `sender_memo_data` - must be 64 bytes +/// * `sender_public_address` - must be a valid `PublicAddress`. +/// * `receiving_subaddress_view_private_key` - must be a valid 32-byte +/// Ristretto-format scalar. +/// * `tx_out_public_key` - must be a valid 32-byte Ristretto-format scalar. +/// +/// # Errors +/// +/// * `LibMcError::InvalidInput` +#[no_mangle] +pub extern "C" fn mc_memo_sender_memo_is_valid( + sender_memo_data: FfiRefPtr, + sender_public_address: FfiRefPtr, + receiving_subaddress_view_private_key: FfiRefPtr, + tx_out_public_key: FfiRefPtr, + out_valid: FfiMutPtr, + out_error: FfiOptMutPtr>, +) -> bool { + ffi_boundary_with_error(out_error, || { + let sender_public_address = PublicAddress::try_from_ffi(&sender_public_address) + .expect("sender_public_address is invalid"); + + let receiving_subaddress_view_private_key = + RistrettoPrivate::try_from_ffi(&receiving_subaddress_view_private_key) + .expect("receiving_subaddress_view_private_key is not a valid RistrettoPrivate"); + + let tx_out_public_key_compressed = + CompressedRistrettoPublic::try_from_ffi(&tx_out_public_key) + .expect("tx_out_public_key is not a valid RistrettoPublic"); + + let memo_data = + <[u8; 64]>::try_from_ffi(&sender_memo_data).expect("sender_memo_data invalid length"); + + let authenticated_sender_memo: AuthenticatedSenderMemo = + AuthenticatedSenderMemo::from(&memo_data); + + let is_memo_valid = authenticated_sender_memo.validate( + &sender_public_address, + &receiving_subaddress_view_private_key, + &tx_out_public_key_compressed, + ); + + *out_valid.into_mut() = bool::from(is_memo_valid); + + Ok(()) + }) +} + +/// # Preconditions +/// +/// * `sender_account_key` - must be a valid account key +/// * `recipient_subaddress_view_public_key` - must be a valid 32-byte +/// Ristretto-format scalar. +/// * `tx_out_public_key` - must be a valid 32-byte Ristretto-format scalar. +/// * `out_memo_data` - length must be >= 64. +/// +/// # Errors +/// +/// * `LibMcError::InvalidInput` +#[no_mangle] +pub extern "C" fn mc_memo_sender_memo_create( + sender_account_key: FfiRefPtr, + recipient_subaddress_view_public_key: FfiRefPtr, + tx_out_public_key: FfiRefPtr, + out_memo_data: FfiMutPtr, + out_error: FfiOptMutPtr>, +) -> bool { + ffi_boundary_with_error(out_error, || { + let sender_account_key = + AccountKey::try_from_ffi(&sender_account_key).expect("account_key is invalid"); + let recipient_subaddress_view_public_key = + RistrettoPublic::try_from_ffi(&recipient_subaddress_view_public_key)?; + let tx_out_public_key = CompressedRistrettoPublic::try_from_ffi(&tx_out_public_key)?; + + let sender_cred = SenderMemoCredential::from(&sender_account_key); + let memo = AuthenticatedSenderMemo::new( + &sender_cred, + &recipient_subaddress_view_public_key, + &tx_out_public_key, + ); + + let memo_bytes: [u8; 64] = memo.clone().into(); + + let out_memo_data = out_memo_data + .into_mut() + .as_slice_mut_of_len(core::mem::size_of_val(&memo_bytes)) + .expect("out_memo_data length is insufficient"); + + out_memo_data.copy_from_slice(&memo_bytes); + Ok(()) + }) +} + +/// # Preconditions +/// +/// * `sender_memo_data` - must be 64 bytes +/// * `out_short_address_hash` - length must be >= 16 bytes +/// +/// # Errors +/// +/// * `LibMcError::InvalidInput` +#[no_mangle] +pub extern "C" fn mc_memo_sender_memo_get_address_hash( + sender_memo_data: FfiRefPtr, + out_short_address_hash: FfiMutPtr, + out_error: FfiOptMutPtr>, +) -> bool { + ffi_boundary_with_error(out_error, || { + let memo_data = + <[u8; 64]>::try_from_ffi(&sender_memo_data).expect("sender_memo_data invalid length"); + + let authenticated_sender_memo: AuthenticatedSenderMemo = + AuthenticatedSenderMemo::from(&memo_data); + + let short_address_hash: ShortAddressHash = authenticated_sender_memo.sender_address_hash(); + + let hash_data: [u8; 16] = short_address_hash.into(); + + let out_short_address_hash = out_short_address_hash + .into_mut() + .as_slice_mut_of_len(core::mem::size_of_val(&hash_data)) + .expect("ShortAddressHash length is insufficient"); + + out_short_address_hash.copy_from_slice(&hash_data); + Ok(()) + }) +} + +/******************************************************************** + * DestinationMemo + */ + +/// # Preconditions +/// +/// * `destination_public_address` - must be a valid 32-byte Ristretto-format +/// scalar. +/// * `number_of_recipients` - must be > 0 +/// * `out_memo_data` - length must be >= 64. +/// +/// # Errors +/// +/// * `LibMcError::InvalidInput` +#[no_mangle] +pub extern "C" fn mc_memo_destination_memo_create( + destination_public_address: FfiRefPtr, + number_of_recipients: u8, + fee: u64, + total_outlay: u64, + out_memo_data: FfiMutPtr, + out_error: FfiOptMutPtr>, +) -> bool { + ffi_boundary_with_error(out_error, || { + let destination_public_address = PublicAddress::try_from_ffi(&destination_public_address) + .expect("destination_public_address is invalid"); + + let mut memo = DestinationMemo::new( + ShortAddressHash::from(&destination_public_address), + total_outlay, + fee, + ) + .unwrap(); + + memo.set_num_recipients(number_of_recipients); + + let memo_bytes: [u8; 64] = memo.clone().into(); + + let out_memo_data = out_memo_data + .into_mut() + .as_slice_mut_of_len(core::mem::size_of_val(&memo_bytes)) + .expect("out_memo_data length is insufficient"); + + out_memo_data.copy_from_slice(&memo_bytes); + Ok(()) + }) +} + +/// # Preconditions +/// +/// * `destination_memo_data` - must be 64 bytes +/// * `out_short_address_hash` - length must be >= 16 bytes +/// +/// # Errors +/// +/// * `LibMcError::InvalidInput` +#[no_mangle] +pub extern "C" fn mc_memo_destination_memo_get_address_hash( + destination_memo_data: FfiRefPtr, + out_short_address_hash: FfiMutPtr, + out_error: FfiOptMutPtr>, +) -> bool { + ffi_boundary_with_error(out_error, || { + let memo_data = <[u8; 64]>::try_from_ffi(&destination_memo_data) + .expect("destination_memo_data invalid length"); + + let destination_memo: DestinationMemo = DestinationMemo::from(&memo_data); + + let short_address_hash: &ShortAddressHash = destination_memo.get_address_hash(); + + let hash_data: [u8; 16] = <[u8; 16]>::from(short_address_hash.clone()); + + let out_short_address_hash = out_short_address_hash + .into_mut() + .as_slice_mut_of_len(core::mem::size_of_val(&hash_data)) + .expect("ShortAddressHash length is insufficient"); + + out_short_address_hash.copy_from_slice(&hash_data); + + Ok(()) + }) +} + +/// # Preconditions +/// +/// * `destination_memo_data` - must be 64 bytes +/// +/// # Errors +/// +/// * `LibMcError::InvalidInput` +#[no_mangle] +pub extern "C" fn mc_memo_destination_memo_get_number_of_recipients( + destination_memo_data: FfiRefPtr, + out_number_of_recipients: FfiMutPtr, + out_error: FfiOptMutPtr>, +) -> bool { + ffi_boundary_with_error(out_error, || { + let memo_data = <[u8; 64]>::try_from_ffi(&destination_memo_data) + .expect("destination_memo_data invalid length"); + + let destination_memo: DestinationMemo = DestinationMemo::from(&memo_data); + + let number_of_recipients: u8 = destination_memo.get_num_recipients().clone(); + + *out_number_of_recipients.into_mut() = number_of_recipients; + + Ok(()) + }) +} + +/// # Preconditions +/// +/// * `destination_memo_data` - must be 64 bytes +/// +/// # Errors +/// +/// * `LibMcError::InvalidInput` +#[no_mangle] +pub extern "C" fn mc_memo_destination_memo_get_fee( + destination_memo_data: FfiRefPtr, + out_fee: FfiMutPtr, + out_error: FfiOptMutPtr>, +) -> bool { + ffi_boundary_with_error(out_error, || { + let memo_data = <[u8; 64]>::try_from_ffi(&destination_memo_data) + .expect("destination_memo_data invalid length"); + + let destination_memo: DestinationMemo = DestinationMemo::from(&memo_data); + + let fee: u64 = destination_memo.get_fee().clone(); + + *out_fee.into_mut() = fee; + + Ok(()) + }) +} + +/// # Preconditions +/// +/// * `destination_memo_data` - must be 64 bytes +/// +/// # Errors +/// +/// * `LibMcError::InvalidInput` +#[no_mangle] +pub extern "C" fn mc_memo_destination_memo_get_total_outlay( + destination_memo_data: FfiRefPtr, + out_total_outlay: FfiMutPtr, + out_error: FfiOptMutPtr>, +) -> bool { + ffi_boundary_with_error(out_error, || { + let memo_data = <[u8; 64]>::try_from_ffi(&destination_memo_data) + .expect("destination_memo_data invalid length"); + + let destination_memo: DestinationMemo = DestinationMemo::from(&memo_data); + + let total_outlay: u64 = destination_memo.get_total_outlay(); + + *out_total_outlay.into_mut() = total_outlay; + + Ok(()) + }) +} + +/******************************************************************** + * SenderWithPaymentRequestMemo + */ + +/// # Preconditions +/// +/// * `sender_with_payment_request_memo_data` - must be 64 bytes +/// * `sender_public_address` - must be a valid `PublicAddress`. +/// * `receiving_subaddress_view_private_key` - must be a valid 32-byte +/// Ristretto-format scalar. +/// * `tx_out_public_key` - must be a valid 32-byte Ristretto-format scalar. +/// +/// # Errors +/// +/// * `LibMcError::InvalidInput` +#[no_mangle] +pub extern "C" fn mc_memo_sender_with_payment_request_memo_is_valid( + sender_with_payment_request_memo_data: FfiRefPtr, + sender_public_address: FfiRefPtr, + receiving_subaddress_view_private_key: FfiRefPtr, + tx_out_public_key: FfiRefPtr, + out_valid: FfiMutPtr, + out_error: FfiOptMutPtr>, +) -> bool { + ffi_boundary_with_error(out_error, || { + let sender_public_address = PublicAddress::try_from_ffi(&sender_public_address) + .expect("sender_public_address is invalid"); + + let receiving_subaddress_view_private_key = + RistrettoPrivate::try_from_ffi(&receiving_subaddress_view_private_key) + .expect("receiving_subaddress_view_private_key is not a valid RistrettoPrivate"); + + let tx_out_public_key_compressed = + CompressedRistrettoPublic::try_from_ffi(&tx_out_public_key) + .expect("tx_out_public_key is not a valid RistrettoPublic"); + + let memo_data = <[u8; 64]>::try_from_ffi(&sender_with_payment_request_memo_data) + .expect("sender_with_payment_request_memo_data invalid length"); + + let authenticated_sender_with_payment_request_memo: AuthenticatedSenderWithPaymentRequestIdMemo = + AuthenticatedSenderWithPaymentRequestIdMemo::from(&memo_data); + + let is_memo_valid = authenticated_sender_with_payment_request_memo.validate( + &sender_public_address, + &receiving_subaddress_view_private_key, + &tx_out_public_key_compressed, + ); + + *out_valid.into_mut() = bool::from(is_memo_valid); + + Ok(()) + }) +} + +/// # Preconditions +/// +/// * `sender_account_key` - must be a valid account key +/// * `recipient_subaddress_view_public_key` - must be a valid 32-byte +/// Ristretto-format scalar. +/// * `tx_out_public_key` - must be a valid 32-byte Ristretto-format scalar. +/// * `out_memo_data` - length must be >= 64. +/// +/// # Errors +/// +/// * `LibMcError::InvalidInput` +#[no_mangle] +pub extern "C" fn mc_memo_sender_with_payment_request_memo_create( + sender_account_key: FfiRefPtr, + recipient_subaddress_view_public_key: FfiRefPtr, + tx_out_public_key: FfiRefPtr, + payment_request_id: u64, + out_memo_data: FfiMutPtr, + out_error: FfiOptMutPtr>, +) -> bool { + ffi_boundary_with_error(out_error, || { + let sender_account_key = + AccountKey::try_from_ffi(&sender_account_key).expect("account_key is invalid"); + let recipient_subaddress_view_public_key = + RistrettoPublic::try_from_ffi(&recipient_subaddress_view_public_key)?; + let tx_out_public_key = CompressedRistrettoPublic::try_from_ffi(&tx_out_public_key)?; + + let sender_cred = SenderMemoCredential::from(&sender_account_key); + let memo = AuthenticatedSenderWithPaymentRequestIdMemo::new( + &sender_cred, + &recipient_subaddress_view_public_key, + &tx_out_public_key, + payment_request_id, + ); + + let memo_bytes: [u8; 64] = memo.clone().into(); + + let out_memo_data = out_memo_data + .into_mut() + .as_slice_mut_of_len(core::mem::size_of_val(&memo_bytes)) + .expect("out_memo_data length is insufficient"); + + out_memo_data.copy_from_slice(&memo_bytes); + + Ok(()) + }) +} + +/// # Preconditions +/// +/// * `sender_with_payment_request_memo_data` - must be 64 bytes +/// * `out_short_address_hash` - length must be >= 16 bytes +/// +/// # Errors +/// +/// * `LibMcError::InvalidInput` +#[no_mangle] +pub extern "C" fn mc_memo_sender_with_payment_request_memo_get_address_hash( + sender_with_payment_request_memo_data: FfiRefPtr, + out_short_address_hash: FfiMutPtr, + out_error: FfiOptMutPtr>, +) -> bool { + ffi_boundary_with_error(out_error, || { + let memo_data = <[u8; 64]>::try_from_ffi(&sender_with_payment_request_memo_data) + .expect("sender_with_payment_request_memo_data invalid length"); + + let authenticated_sender_with_payment_request_memo: AuthenticatedSenderWithPaymentRequestIdMemo = + AuthenticatedSenderWithPaymentRequestIdMemo::from(&memo_data); + + let short_address_hash: ShortAddressHash = + authenticated_sender_with_payment_request_memo.sender_address_hash(); + + let hash_data: [u8; 16] = short_address_hash.into(); + + let out_short_address_hash = out_short_address_hash + .into_mut() + .as_slice_mut_of_len(core::mem::size_of_val(&hash_data)) + .expect("ShortAddressHash length is insufficient"); + + out_short_address_hash.copy_from_slice(&hash_data); + + Ok(()) + }) +} + +/// # Preconditions +/// +/// * `sender_with_payment_request_memo_data` - must be 64 bytes +/// +/// # Errors +/// +/// * `LibMcError::InvalidInput` +#[no_mangle] +pub extern "C" fn mc_memo_sender_with_payment_request_memo_get_payment_request_id( + sender_with_payment_request_memo_data: FfiRefPtr, + out_payment_request_id: FfiMutPtr, + out_error: FfiOptMutPtr>, +) -> bool { + ffi_boundary_with_error(out_error, || { + let memo_data = <[u8; 64]>::try_from_ffi(&sender_with_payment_request_memo_data) + .expect("sender_with_payment_request_memo_data invalid length"); + + let sender_with_payment_request_memo: AuthenticatedSenderWithPaymentRequestIdMemo = + AuthenticatedSenderWithPaymentRequestIdMemo::from(&memo_data); + + let payment_request_id: u64 = sender_with_payment_request_memo + .payment_request_id() + .clone(); + + *out_payment_request_id.into_mut() = payment_request_id; + + Ok(()) + }) +} + +/******************************************************************** + * Decrypt Memo Payload + */ + +/// # Preconditions +/// +/// * `encrypted_memo` - must be 66 bytes +/// * `tx_out_public_key` - must be a valid 32-byte Ristretto-format scalar. +/// * `account_key` - must be a valid account key +/// * `out_memo_payload` - length must be >= 16 bytes +/// +/// # Errors +/// +/// * `LibMcError::InvalidInput` +#[no_mangle] +pub extern "C" fn mc_memo_decrypt_e_memo_payload( + encrypted_memo: FfiRefPtr, + tx_out_public_key: FfiRefPtr, + account_key: FfiRefPtr, + out_memo_payload: FfiMutPtr, + out_error: FfiOptMutPtr>, +) -> bool { + ffi_boundary_with_error(out_error, || { + let tx_out_public_key = RistrettoPublic::try_from_ffi(&tx_out_public_key)?; + let account_key_obj = + AccountKey::try_from_ffi(&account_key).expect("account_key is invalid"); + let e_memo = EncryptedMemo::try_from_ffi(&encrypted_memo)?; + let shared_secret = + get_tx_out_shared_secret(&*account_key_obj.view_private_key(), &tx_out_public_key); + + let memo_payload: MemoPayload = e_memo.decrypt(&shared_secret); + let memo_payload_generic_array: GenericArray = memo_payload.into(); + + let out_memo_payload = out_memo_payload + .into_mut() + .as_slice_mut_of_len(core::mem::size_of_val(&memo_payload_generic_array)) + .expect("Memo payload length is insufficient"); + + out_memo_payload.copy_from_slice(&memo_payload_generic_array); + Ok(()) + }) +} + +/******************************************************************** + * Trait Implementations + */ + impl<'a> TryFromFfi<&McBuffer<'a>> for CompressedCommitment { type Error = LibMcError; @@ -552,3 +1181,28 @@ impl<'a> TryFromFfi<&McBuffer<'a>> for TxOutConfirmationNumber { Ok(TxOutConfirmationNumber::from(confirmation_number)) } } + +/* ==== Ristretto ==== */ + +impl<'a> TryFromFfi<&McBuffer<'a>> for CompressedRistrettoPublic { + type Error = LibMcError; + + fn try_from_ffi(src: &McBuffer<'a>) -> Result { + let src = <&[u8; 32]>::try_from_ffi(src)?; + CompressedRistrettoPublic::try_from(src) + .map_err(|err| LibMcError::InvalidInput(format!("{:?}", err))) + } +} + +/* ==== EncryptedMemo ==== */ + +impl<'a> TryFromFfi<&McBuffer<'a>> for EncryptedMemo { + type Error = LibMcError; + + fn try_from_ffi(src: &McBuffer<'a>) -> Result { + let src = <&[u8; 66]>::try_from_ffi(src)?; + let memo_payload_generic_array: GenericArray = GenericArray::clone_from_slice(src); + EncryptedMemo::try_from(memo_payload_generic_array) + .map_err(|err| LibMcError::InvalidInput(format!("{:?}", err))) + } +} diff --git a/libmobilecoin/toolchain-config.env b/libmobilecoin/toolchain-config.env new file mode 100644 index 0000000000..5da8bc66f2 --- /dev/null +++ b/libmobilecoin/toolchain-config.env @@ -0,0 +1,9 @@ +# Parameters used to compile rust-bitcode toolchains +# +# Storing seperately so they can be checksummed +# and used as a cache key in circleci +# +export RUST_BITCODE_COMMIT="5ed1ddf8b708c512c589d10453193d12a4643169" +export RUST_COMMIT="4e282795d" +export RUST_COMMIT_NAME="nightly-2021-08-01" +export SWIFT_VERSION="5.3.2" From 952af636dccad8ba358a4fca3c04df5b0ca96068 Mon Sep 17 00:00:00 2001 From: the-real-adammork Date: Mon, 4 Apr 2022 22:29:55 -0700 Subject: [PATCH 2/8] revert version change --- libmobilecoin/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libmobilecoin/Cargo.toml b/libmobilecoin/Cargo.toml index c6e549261c..303ac65737 100644 --- a/libmobilecoin/Cargo.toml +++ b/libmobilecoin/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libmobilecoin" -version = "1.3.0-pre0" +version = "1.2.0-pre2" authors = ["MobileCoin"] edition = "2018" From a5381181707dc4cf72321f02e69a523a4f605519 Mon Sep 17 00:00:00 2001 From: the-real-adammork Date: Tue, 5 Apr 2022 01:18:00 -0700 Subject: [PATCH 3/8] revert version change --- libmobilecoin/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libmobilecoin/Cargo.toml b/libmobilecoin/Cargo.toml index c6e549261c..303ac65737 100644 --- a/libmobilecoin/Cargo.toml +++ b/libmobilecoin/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libmobilecoin" -version = "1.3.0-pre0" +version = "1.2.0-pre2" authors = ["MobileCoin"] edition = "2018" From 503b76f1cd231b485bc792719f485046a9cb476a Mon Sep 17 00:00:00 2001 From: the-real-adammork Date: Tue, 5 Apr 2022 01:18:27 -0700 Subject: [PATCH 4/8] update cargo lock --- Cargo.lock | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index b18dccdaac..9cbf354e4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2282,6 +2282,8 @@ dependencies = [ "cmake", "crc", "displaydoc", + "generic-array 0.14.5", + "hex", "libc", "mc-account-keys", "mc-account-keys-slip10", From 28323d761a0cd83cb1fed2c9dd3e534a4fa90b98 Mon Sep 17 00:00:00 2001 From: the-real-adammork Date: Tue, 5 Apr 2022 02:12:12 -0700 Subject: [PATCH 5/8] re-order dependencies --- libmobilecoin/Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libmobilecoin/Cargo.toml b/libmobilecoin/Cargo.toml index 303ac65737..8529058556 100644 --- a/libmobilecoin/Cargo.toml +++ b/libmobilecoin/Cargo.toml @@ -11,17 +11,17 @@ crate-type = ["lib", "staticlib", "cdylib"] [dependencies] # External dependencies aes-gcm = "0.9.4" +crc = "2.1.0" displaydoc = "0.2" +generic-array = { version = "0.14", features = ["serde", "more_lengths"] } +hex = "0.4.3" libc = "0.2" protobuf = "2.22.1" -crc = "2.1.0" rand_core = { version = "0.6", features = ["std"] } sha2 = "0.9.8" slip10_ed25519 = "0.1.3" tiny-bip39 = "0.8" zeroize = "1.5" -hex = "0.4.3" -generic-array = { version = "0.14", features = ["serde", "more_lengths"] } # Lock a specific cmake version that plays nicely with iOS. Note that 0.1.45 does not actually do that, # but there is an override to a specific commit of a currently-unreleased version in the root Cargo.toml. From 0eecdccf0033238887a41198b526eee448d0340d Mon Sep 17 00:00:00 2001 From: the-real-adammork Date: Thu, 7 Apr 2022 00:45:01 -0700 Subject: [PATCH 6/8] fix cargo clippy issues --- libmobilecoin/src/transaction.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/libmobilecoin/src/transaction.rs b/libmobilecoin/src/transaction.rs index dcb6afc037..15c61d8fa3 100644 --- a/libmobilecoin/src/transaction.rs +++ b/libmobilecoin/src/transaction.rs @@ -737,7 +737,7 @@ pub extern "C" fn mc_memo_sender_memo_create( &tx_out_public_key, ); - let memo_bytes: [u8; 64] = memo.clone().into(); + let memo_bytes: [u8; 64] = memo.into(); let out_memo_data = out_memo_data .into_mut() @@ -886,7 +886,7 @@ pub extern "C" fn mc_memo_destination_memo_get_number_of_recipients( let destination_memo: DestinationMemo = DestinationMemo::from(&memo_data); - let number_of_recipients: u8 = destination_memo.get_num_recipients().clone(); + let number_of_recipients: u8 = destination_memo.get_num_recipients(); *out_number_of_recipients.into_mut() = number_of_recipients; @@ -913,7 +913,7 @@ pub extern "C" fn mc_memo_destination_memo_get_fee( let destination_memo: DestinationMemo = DestinationMemo::from(&memo_data); - let fee: u64 = destination_memo.get_fee().clone(); + let fee: u64 = destination_memo.get_fee(); *out_fee.into_mut() = fee; @@ -1037,7 +1037,7 @@ pub extern "C" fn mc_memo_sender_with_payment_request_memo_create( payment_request_id, ); - let memo_bytes: [u8; 64] = memo.clone().into(); + let memo_bytes: [u8; 64] = memo.into(); let out_memo_data = out_memo_data .into_mut() @@ -1108,8 +1108,7 @@ pub extern "C" fn mc_memo_sender_with_payment_request_memo_get_payment_request_i AuthenticatedSenderWithPaymentRequestIdMemo::from(&memo_data); let payment_request_id: u64 = sender_with_payment_request_memo - .payment_request_id() - .clone(); + .payment_request_id(); *out_payment_request_id.into_mut() = payment_request_id; From 85b89ae3fb69d42052456cbc2bfad5ab49132af8 Mon Sep 17 00:00:00 2001 From: the-real-adammork Date: Thu, 7 Apr 2022 01:31:38 -0700 Subject: [PATCH 7/8] reformat after lint --- libmobilecoin/src/transaction.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libmobilecoin/src/transaction.rs b/libmobilecoin/src/transaction.rs index 15c61d8fa3..1102486085 100644 --- a/libmobilecoin/src/transaction.rs +++ b/libmobilecoin/src/transaction.rs @@ -1107,8 +1107,7 @@ pub extern "C" fn mc_memo_sender_with_payment_request_memo_get_payment_request_i let sender_with_payment_request_memo: AuthenticatedSenderWithPaymentRequestIdMemo = AuthenticatedSenderWithPaymentRequestIdMemo::from(&memo_data); - let payment_request_id: u64 = sender_with_payment_request_memo - .payment_request_id(); + let payment_request_id: u64 = sender_with_payment_request_memo.payment_request_id(); *out_payment_request_id.into_mut() = payment_request_id; From 9ed8243915c79323cf5e0850d91e78bb7ee62f35 Mon Sep 17 00:00:00 2001 From: the-real-adammork Date: Thu, 7 Apr 2022 02:00:09 -0700 Subject: [PATCH 8/8] update cbindgen --- libmobilecoin/libmobilecoin_cbindgen.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/libmobilecoin/libmobilecoin_cbindgen.h b/libmobilecoin/libmobilecoin_cbindgen.h index f4128365a1..287a115c97 100644 --- a/libmobilecoin/libmobilecoin_cbindgen.h +++ b/libmobilecoin/libmobilecoin_cbindgen.h @@ -868,8 +868,8 @@ void mc_memo_builder_free(FfiOptOwnedPtr memo_builder); * * * `sender_memo_data` - must be 64 bytes * * `sender_public_address` - must be a valid `PublicAddress`. - * * `receiving_subaddress_view_private_key` - must be a valid - * 32-byte Ristretto-format scalar. + * * `receiving_subaddress_view_private_key` - must be a valid 32-byte + * Ristretto-format scalar. * * `tx_out_public_key` - must be a valid 32-byte Ristretto-format scalar. * * # Errors @@ -887,8 +887,8 @@ bool mc_memo_sender_memo_is_valid(FfiRefPtr sender_memo_data, * # Preconditions * * * `sender_account_key` - must be a valid account key - * * `recipient_subaddress_view_public_key` - must be a valid - * 32-byte Ristretto-format scalar. + * * `recipient_subaddress_view_public_key` - must be a valid 32-byte + * Ristretto-format scalar. * * `tx_out_public_key` - must be a valid 32-byte Ristretto-format scalar. * * `out_memo_data` - length must be >= 64. * @@ -919,8 +919,8 @@ bool mc_memo_sender_memo_get_address_hash(FfiRefPtr sender_memo_data, /** * # Preconditions * - * * `destination_public_address` - must be a valid 32-byte - * Ristretto-format scalar. + * * `destination_public_address` - must be a valid 32-byte Ristretto-format + * scalar. * * `number_of_recipients` - must be > 0 * * `out_memo_data` - length must be >= 64. * @@ -993,8 +993,8 @@ bool mc_memo_destination_memo_get_total_outlay(FfiRefPtr destination_m * * * `sender_with_payment_request_memo_data` - must be 64 bytes * * `sender_public_address` - must be a valid `PublicAddress`. - * * `receiving_subaddress_view_private_key` - must be a valid - * 32-byte Ristretto-format scalar. + * * `receiving_subaddress_view_private_key` - must be a valid 32-byte + * Ristretto-format scalar. * * `tx_out_public_key` - must be a valid 32-byte Ristretto-format scalar. * * # Errors @@ -1012,8 +1012,8 @@ bool mc_memo_sender_with_payment_request_memo_is_valid(FfiRefPtr sende * # Preconditions * * * `sender_account_key` - must be a valid account key - * * `recipient_subaddress_view_public_key` - must be a valid - * 32-byte Ristretto-format scalar. + * * `recipient_subaddress_view_public_key` - must be a valid 32-byte + * Ristretto-format scalar. * * `tx_out_public_key` - must be a valid 32-byte Ristretto-format scalar. * * `out_memo_data` - length must be >= 64. *