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

Nonce & remame Execution summary deposits/withdrawals #41

Merged
merged 2 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
extension Nonce {
public static func secureRandom() -> Self {
newNonceRandom()
}

public var value: UInt32 {
nonceGetValue(nonce: self)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#if DEBUG
extension Nonce {
public static let sample: Self = newNonceSample()
public static let sampleOther: Self = newNonceSampleOther()
}
#endif // DEBUG
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
extension Nonce: @unchecked Sendable {}
extension Nonce: SargonModel {}
13 changes: 13 additions & 0 deletions apple/Tests/TestCases/Crypto/NonceTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
final class NonceTests: Test<Nonce> {

// with very low probability this will fail
func test_secure_random() {
let n = 10
let sut = Set<SUT>(
(0..<n).map { _ in SUT.secureRandom() }
)
XCTAssertEqual(sut.count, n)
XCTAssertEqual(Set(sut.map(\.value)).count, n)
}

}
2 changes: 2 additions & 0 deletions src/core/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mod locale_config;
mod logged_result;
mod non_empty_max_n_bytes;
mod nonce;
mod nonce_uniffi_fn;
mod rounding_mode;
mod safe_to_log;
mod signatures;
Expand All @@ -28,6 +29,7 @@ pub use locale_config::*;
pub use logged_result::*;
pub use non_empty_max_n_bytes::*;
pub use nonce::*;
pub use nonce_uniffi_fn::*;
pub use rounding_mode::*;
pub use safe_to_log::*;
pub use signatures::*;
113 changes: 96 additions & 17 deletions src/core/types/nonce.rs
Original file line number Diff line number Diff line change
@@ -1,45 +1,102 @@
pub use crate::prelude::*;

// Generate the FfiConverter needed by UniFFI for newtype `Nonce`.
uniffi::custom_newtype!(Nonce, u32);
uniffi::custom_newtype!(NonceSecretMagic, u32);

/// A random number generated part of a transaction header,
/// ensuring every transaction os unique even though its
/// transaction manifest might be equal. This nonce is
/// generated by wallets for incoming transactions.
#[derive(
Clone, Copy, PartialEq, Eq, Hash, derive_more::Display, derive_more::Debug,
)]
pub struct Nonce(pub u32);
pub struct NonceSecretMagic(pub u32);

impl Nonce {
impl NonceSecretMagic {
/// Generates 4 random bytes and creates a Nonce (u32) from it.
pub fn random() -> Self {
u32::from_be_bytes(
let value = u32::from_be_bytes(
generate_bytes::<4>()
.as_slice()
.try_into()
.expect("It is 4 bytes."),
)
.into()
);

Self(value)
}
}

/// A random number generated part of a transaction header,
/// ensuring every transaction os unique even though its
/// transaction manifest might be equal. This nonce is
/// generated by wallets for incoming transactions.
#[derive(
Clone,
Copy,
PartialEq,
Eq,
Hash,
derive_more::Display,
derive_more::Debug,
uniffi::Record,
)]
#[display("{}", self.secret_magic)]
#[debug("{}", self.secret_magic)]
pub struct Nonce {
secret_magic: NonceSecretMagic,
}

impl From<NonceSecretMagic> for Nonce {
fn from(value: NonceSecretMagic) -> Self {
Self {
secret_magic: value,
}
}
}

impl From<u32> for Nonce {
fn from(value: u32) -> Self {
Self(value)
Self::from(NonceSecretMagic(value))
}
}

impl From<Nonce> for u32 {
fn from(value: Nonce) -> Self {
value.0
value.secret_magic.0
}
}

impl Nonce {
/// Generates 4 random bytes and creates a Nonce (u32) from it.
pub fn random() -> Self {
Self::from(NonceSecretMagic::random())
}
}

impl HasSampleValues for Nonce {
fn sample() -> Self {
Self::from(NonceSecretMagic(123456789))
}

fn sample_other() -> Self {
Self::from(NonceSecretMagic(987654321))
}
}

#[cfg(test)]
mod tests {
use crate::prelude::*;
use super::*;

#[allow(clippy::upper_case_acronyms)]
type SUT = Nonce;

#[test]
#[ignore]
fn equality() {
assert_eq!(SUT::sample(), SUT::sample());
assert_eq!(SUT::sample_other(), SUT::sample_other());
}

#[test]
fn inequality() {
assert_ne!(SUT::sample(), SUT::sample_other());
}

#[test]
fn from_u32() {
Expand All @@ -50,10 +107,21 @@ mod tests {
test(1337);
}

#[test]
fn display() {
assert_eq!(format!("{}", SUT::sample()), "123456789");
}

#[test]
fn debug() {
assert_eq!(format!("{:?}", SUT::sample()), "123456789");
}

#[test]
fn to_u32() {
let test =
|u: u32| assert_eq!(Nonce::from(u32::from(Nonce::from(u))).0, u);
let test = |u: u32| {
assert_eq!(u32::from(Nonce::from(u32::from(Nonce::from(u)))), u)
};
test(0);
test(1);
test(2);
Expand All @@ -62,12 +130,23 @@ mod tests {

#[test]
fn generate_new() {
let mut set: HashSet<Nonce> = HashSet::new();
let mut set: HashSet<SUT> = HashSet::new();
let n = 100;
for _ in 0..n {
let sut = Nonce::random();
let sut = SUT::random();
set.insert(sut);
}
assert_eq!(set.len(), n); // with a low probability this might fail yes.
}

#[test]
fn manual_perform_uniffi_conversion() {
let sut = SUT::sample();
let ffi_side =
<NonceSecretMagic as crate::UniffiCustomTypeConverter>::from_custom(
sut.secret_magic,
);
let from_ffi_side = <NonceSecretMagic as crate::UniffiCustomTypeConverter>::into_custom(ffi_side).unwrap();
assert_eq!(sut, from_ffi_side.into());
}
}
78 changes: 78 additions & 0 deletions src/core/types/nonce_uniffi_fn.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use crate::prelude::*;

#[uniffi::export]
pub fn new_nonce_random() -> Nonce {
Nonce::random()
}

#[uniffi::export]
pub fn new_nonce_from_u32(value: u32) -> Nonce {
Nonce::from(value)
}

#[uniffi::export]
pub fn new_nonce_sample() -> Nonce {
Nonce::sample()
}

#[uniffi::export]
pub fn new_nonce_sample_other() -> Nonce {
Nonce::sample_other()
}

#[uniffi::export]
pub fn nonce_get_value(nonce: Nonce) -> u32 {
u32::from(nonce)
}

#[cfg(test)]
mod tests {
use super::*;

#[allow(clippy::upper_case_acronyms)]
type SUT = Nonce;

#[test]
fn hash_of_samples() {
assert_eq!(
HashSet::<SUT>::from_iter([
new_nonce_sample(),
new_nonce_sample_other(),
// duplicates should get removed
new_nonce_sample(),
new_nonce_sample_other(),
])
.len(),
2
);
}

#[test]
fn random() {
// with very low probability this will fail.
assert_ne!(new_nonce_random(), new_nonce_random())
}

#[test]
fn from_u32() {
let test = |u: u32| assert_eq!(u32::from(new_nonce_from_u32(u)), u);
test(0);
test(1);
test(2);
test(1337);
}

#[test]
fn to_u32() {
let test = |u: u32| {
assert_eq!(
nonce_get_value(Nonce::from(u32::from(Nonce::from(u)))),
u
)
};
test(0);
test(1);
test(2);
test(1337);
}
}
4 changes: 2 additions & 2 deletions src/wrapped_radix_engine_toolkit/high_level/ret_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -425,9 +425,9 @@ mod tests {

#[test]
fn test_debug_print_compiled_notarized_intent() {
assert_eq!(
pretty_assertions::assert_eq!(
debug_print_compiled_notarized_intent(CompiledNotarizedIntent::sample()),
"NotarizedTransaction { signed_intent: SignedIntent { intent: header:\nTransactionHeader { network_id: Mainnet, start_epoch_inclusive: Epoch(76935), end_epoch_exclusive: Epoch(76945), nonce: Nonce(2371337), notary_public_key: Ed25519 { value: ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf }, notary_is_signatory: true, tip_percentage: 0 }\n\nmessage:\nPlainText { plaintext: PlaintextMessage { mime_type: \"text/plain\", message: StringMessage { string: \"Hello Radix!\" } } }\n\nmanifest:\nCALL_METHOD\n Address(\"account_rdx16xlfcpp0vf7e3gqnswv8j9k58n6rjccu58vvspmdva22kf3aplease\")\n \"lock_fee\"\n Decimal(\"0.61\")\n;\nCALL_METHOD\n Address(\"account_rdx16xlfcpp0vf7e3gqnswv8j9k58n6rjccu58vvspmdva22kf3aplease\")\n \"withdraw\"\n Address(\"resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd\")\n Decimal(\"1337\")\n;\nTAKE_FROM_WORKTOP\n Address(\"resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd\")\n Decimal(\"1337\")\n Bucket(\"bucket1\")\n;\nCALL_METHOD\n Address(\"account_rdx16yf8jxxpdtcf4afpj5ddeuazp2evep7quuhgtq28vjznee08master\")\n \"try_deposit_or_abort\"\n Bucket(\"bucket1\")\n Enum<0u8>()\n;\n\n\n, intent_signatures: IntentSignatures { signatures: [] } }, notary_signature: NotarySignature { secret_magic: Ed25519 { value: 839ac9c47db45950fc0cd453c5ebbbfa7ae5f7c20753abe2370b5b40fdee89e522c4d810d060e0c56211d036043fd32b9908e97bf114c1835ca02d74018fdd09 } } }"
"NotarizedTransaction { signed_intent: SignedIntent { intent: header:\nTransactionHeader { network_id: Mainnet, start_epoch_inclusive: Epoch(76935), end_epoch_exclusive: Epoch(76945), nonce: 2371337, notary_public_key: Ed25519 { value: ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf }, notary_is_signatory: true, tip_percentage: 0 }\n\nmessage:\nPlainText { plaintext: PlaintextMessage { mime_type: \"text/plain\", message: StringMessage { string: \"Hello Radix!\" } } }\n\nmanifest:\nCALL_METHOD\n Address(\"account_rdx16xlfcpp0vf7e3gqnswv8j9k58n6rjccu58vvspmdva22kf3aplease\")\n \"lock_fee\"\n Decimal(\"0.61\")\n;\nCALL_METHOD\n Address(\"account_rdx16xlfcpp0vf7e3gqnswv8j9k58n6rjccu58vvspmdva22kf3aplease\")\n \"withdraw\"\n Address(\"resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd\")\n Decimal(\"1337\")\n;\nTAKE_FROM_WORKTOP\n Address(\"resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd\")\n Decimal(\"1337\")\n Bucket(\"bucket1\")\n;\nCALL_METHOD\n Address(\"account_rdx16yf8jxxpdtcf4afpj5ddeuazp2evep7quuhgtq28vjznee08master\")\n \"try_deposit_or_abort\"\n Bucket(\"bucket1\")\n Enum<0u8>()\n;\n\n\n, intent_signatures: IntentSignatures { signatures: [] } }, notary_signature: NotarySignature { secret_magic: Ed25519 { value: 839ac9c47db45950fc0cd453c5ebbbfa7ae5f7c20753abe2370b5b40fdee89e522c4d810d060e0c56211d036043fd32b9908e97bf114c1835ca02d74018fdd09 } } }"
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@ use crate::prelude::*;
/// wallets present the contents of a transaction.
#[derive(Clone, Debug, PartialEq, Eq, uniffi::Record)]
pub struct ExecutionSummary {
/// Addresses of accounts withdrawn from in the manifest.
pub addresses_of_accounts_withdrawn_from:
HashMap<AccountAddress, Vec<ResourceIndicator>>,
/// Per account, a list of all token balances that has been withdrawn from that account.
pub withdrawals: HashMap<AccountAddress, Vec<ResourceIndicator>>,

/// Addresses of accounts deposited into in the manifest.
pub addresses_of_accounts_deposited_into:
HashMap<AccountAddress, Vec<ResourceIndicator>>,
/// Per account, a list of all token balances that has been deposited into that account.
pub deposits: HashMap<AccountAddress, Vec<ResourceIndicator>>,

/// Addresses of accounts encountered in the manifest where privileged
/// methods were called. The wallets will need to collect signatures
Expand Down Expand Up @@ -78,10 +76,8 @@ impl ExecutionSummary {
new_entities: impl Into<NewEntities>,
) -> Self {
Self {
addresses_of_accounts_withdrawn_from:
addresses_of_accounts_withdrawn_from.into(),
addresses_of_accounts_deposited_into:
addresses_of_accounts_deposited_into.into(),
withdrawals: addresses_of_accounts_withdrawn_from.into(),
deposits: addresses_of_accounts_deposited_into.into(),
addresses_of_accounts_requiring_auth:
addresses_of_accounts_requiring_auth
.into_iter()
Expand Down
Loading