Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gift Code Memo Builder #1933

5 changes: 5 additions & 0 deletions account-keys/src/account_keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,11 @@ impl AccountKey {
self.subaddress_spend_private(CHANGE_SUBADDRESS_INDEX)
}

/// The private spend key for the gift code subaddress
pub fn gift_code_subaddress_spend_private(&self) -> RistrettoPrivate {
self.subaddress_spend_private(GIFT_CODE_SUBADDRESS_INDEX)
}

/// The private spend key for the i^th subaddress.
pub fn subaddress_spend_private(&self, index: u64) -> RistrettoPrivate {
let a: &Scalar = self.view_private_key.as_ref();
Expand Down
2 changes: 1 addition & 1 deletion account-keys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ mod identity;
pub use crate::{
account_keys::{
AccountKey, PublicAddress, CHANGE_SUBADDRESS_INDEX, DEFAULT_SUBADDRESS_INDEX,
INVALID_SUBADDRESS_INDEX,
GIFT_CODE_SUBADDRESS_INDEX, INVALID_SUBADDRESS_INDEX,
},
address_hash::ShortAddressHash,
burn_address::{burn_address, burn_address_view_private, BURN_ADDRESS_VIEW_PRIVATE},
Expand Down
2 changes: 1 addition & 1 deletion transaction/core/src/memo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ derive_serde_from_repr_bytes!(MemoPayload);
derive_prost_message_from_repr_bytes!(MemoPayload);

/// An error which can occur when handling memos
#[derive(Display, Debug)]
#[derive(Debug, Display, Eq, PartialEq)]
pub enum MemoError {
/// Wrong length for memo payload: {0}
BadLength(usize),
Expand Down
35 changes: 32 additions & 3 deletions transaction/core/src/tx_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

//! Errors that can occur when creating a new TxOut

use crate::AmountError;
use alloc::string::String;
use crate::{AmountError, MemoError};
use alloc::{format, string::String};
use core::str::Utf8Error;
use displaydoc::Display;
use mc_crypto_keys::KeyError;

Expand Down Expand Up @@ -55,7 +56,7 @@ impl From<AmountError> for ViewKeyMatchError {
/// We have included error codes for some known useful error conditions.
/// For a custom MemoBuilder, you can try to reuse those, or use the Other
/// error code.
#[derive(Debug, Display, PartialEq, Eq)]
#[derive(Debug, Display, Eq, PartialEq)]
pub enum NewMemoError {
/// Limits for '{0}' value exceeded
LimitsExceeded(&'static str),
Expand All @@ -71,8 +72,36 @@ pub enum NewMemoError {
MultipleOutputs,
/// Missing output
MissingOutput,
/// Missing required input to build the memo: {0}
MissingInput(String),
/// Mixed Token Ids are not supported in these memos
MixedTokenIds,
/// Destination memo is not supported
DestinationMemoNotAllowed,
/// Improperly configured input: {0}
BadInputs(String),
/// Creation
Creation(MemoError),
/// Utf-8 did not properly decode
Utf8Decoding,
/// Other: {0}
Other(String),
}

impl From<MemoError> for NewMemoError {
fn from(src: MemoError) -> Self {
match src {
MemoError::Utf8Decoding => Self::Utf8Decoding,
MemoError::BadLength(byte_len) => Self::BadInputs(format!(
"Input of length: {} exceeded max byte length",
byte_len
)),
}
}
}

impl From<Utf8Error> for NewMemoError {
fn from(_: Utf8Error) -> Self {
Self::Utf8Decoding
}
}
10 changes: 7 additions & 3 deletions transaction/std/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,14 @@ pub use error::{SignedContingentInputBuilderError, TxBuilderError};
pub use input_credentials::InputCredentials;
pub use memo::{
AuthenticatedSenderMemo, AuthenticatedSenderWithPaymentRequestIdMemo, BurnRedemptionMemo,
DestinationMemo, DestinationMemoError, MemoDecodingError, MemoType, RegisteredMemoType,
SenderMemoCredential, UnusedMemo,
DestinationMemo, DestinationMemoError, GiftCodeCancellationMemo, GiftCodeFundingMemo,
GiftCodeSenderMemo, MemoDecodingError, MemoType, RegisteredMemoType, SenderMemoCredential,
UnusedMemo,
};
pub use memo_builder::{
BurnRedemptionMemoBuilder, EmptyMemoBuilder, GiftCodeCancellationMemoBuilder,
GiftCodeFundingMemoBuilder, GiftCodeSenderMemoBuilder, MemoBuilder, RTHMemoBuilder,
};
pub use memo_builder::{BurnRedemptionMemoBuilder, EmptyMemoBuilder, MemoBuilder, RTHMemoBuilder};
pub use reserved_destination::ReservedDestination;
pub use signed_contingent_input_builder::SignedContingentInputBuilder;
pub use transaction_builder::{DefaultTxOutputsOrdering, TransactionBuilder, TxOutputsOrdering};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright (c) 2018-2022 The MobileCoin Foundation

//! Defines the Memo Builder for the gift code cancellation memo (0x0202)
//! specified in MCIP #32

use super::{memo::GiftCodeCancellationMemo, MemoBuilder, ReservedDestination};
use mc_account_keys::PublicAddress;
use mc_transaction_core::{Amount, MemoContext, MemoPayload, NewMemoError};

/// There are three possible gift code memo types specified in MCIP #32
/// | Memo type bytes | Name |
/// | ----------- | ----------- |
/// | 0x0002 | Gift Code Sender Memo |
/// | 0x0201 | Gift Code Funding Memo |
/// | -->0x0202<-- | Gift Code Cancellation Memo |
/// This memo builder builds a gift code cancellation memo (0x0202). Gift code
/// cancellation is defined as the sender sending the gift code TxOut at the
/// gift code subaddress to their change address prior to it being spent by
/// the receiver. When that happens a gift code cancellation memo is
/// written to the change TxOut that stores the index of the TxOut originally
/// sent to the gift code subaddress when the gift code was funded.
#[derive(Clone, Debug)]
pub struct GiftCodeCancellationMemoBuilder {
// Index of the gift code TxOut that was originally funded
gift_code_tx_out_global_index: u64,
// Whether we've already written the change memo
wrote_change_memo: bool,
}

impl GiftCodeCancellationMemoBuilder {
/// Initialize memo builder with the index of the originally
/// funded gift code TxOut
pub fn new(gift_code_tx_out_global_index: u64) -> Self {
Self {
gift_code_tx_out_global_index,
wrote_change_memo: false,
}
}
}

impl MemoBuilder for GiftCodeCancellationMemoBuilder {
/// Set the fee
fn set_fee(&mut self, _fee: Amount) -> Result<(), NewMemoError> {
Ok(())
}

/// Destination memos for gift code cancellation memos are not allowed
fn make_memo_for_output(
&mut self,
_amount: Amount,
_recipient: &PublicAddress,
_memo_context: MemoContext,
) -> Result<MemoPayload, NewMemoError> {
Err(NewMemoError::DestinationMemoNotAllowed)
}

/// Write the cancellation memo to the change output
fn make_memo_for_change_output(
&mut self,
_amount: Amount,
_change_destination: &ReservedDestination,
_memo_context: MemoContext,
) -> Result<MemoPayload, NewMemoError> {
if self.wrote_change_memo {
return Err(NewMemoError::MultipleChangeOutputs);
}
self.wrote_change_memo = true;
Ok(GiftCodeCancellationMemo::from(self.gift_code_tx_out_global_index).into())
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::build_zero_value_change_memo;
use std::convert::TryInto;

#[test]
fn test_gift_code_cancellation_memo_built_successfully_with_index() {
// Set the cancellation index and create memo builder
let index = 666;
iamalwaysuncomfortable marked this conversation as resolved.
Show resolved Hide resolved
let mut builder = GiftCodeCancellationMemoBuilder::new(index);

// Build the memo payload and get the data
let memo_payload = build_zero_value_change_memo(&mut builder).unwrap();
let memo_data = memo_payload.get_memo_data();

// Verify memo data
let derived_index = u64::from_le_bytes(memo_data[0..8].try_into().unwrap());
iamalwaysuncomfortable marked this conversation as resolved.
Show resolved Hide resolved
assert_eq!(index, derived_index);
}

#[test]
fn test_gift_code_cancellation_memo_fails_for_more_than_one_change_memo() {
// Set the cancellation index and create memo builder
let index = 666;
let mut builder = GiftCodeCancellationMemoBuilder::new(index);

// Attempt to build two change outputs
build_zero_value_change_memo(&mut builder).unwrap();
let memo_payload = build_zero_value_change_memo(&mut builder);

// Assert failure for the second output
assert!(matches!(
memo_payload,
Err(NewMemoError::MultipleChangeOutputs)
));
}
}
Loading