Skip to content
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
50 changes: 50 additions & 0 deletions packages/dashpay-contract/schema/v3/dashpay.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -297,5 +297,55 @@
"accountReference"
],
"additionalProperties": false
},
"privateContactRequest": {
"type": "object",
"description": "A contact request nobody else can see: an entry encrypted to the recipient's DashPay decryption key and appended, without an identity, by a transition paid from the shielded pool. These properties describe the plaintext the recipient decrypts; the chain never reads it.",
"private": {
"entrySize": 589
},
"documentsMutable": false,
"canBeDeleted": false,
"properties": {
"accountReference": {
"type": "integer",
"minimum": 0,
"maximum": 4294967295,
"position": 0
},
"extendedPublicKey": {
"type": "array",
"byteArray": true,
"minItems": 69,
"maxItems": 69,
"description": "The DIP-15 compact extended public key the recipient pays to: the parent fingerprint (4 bytes), the chain code (32) and the public key (33).",
"position": 1
},
"accountLabel": {
"type": "string",
"maxBytes": 63,
"position": 2
},
"autoAcceptProof": {
"type": "array",
"byteArray": true,
"minItems": 38,
"maxItems": 102,
"position": 3
},
"shieldedAddress": {
"type": "array",
"byteArray": true,
"minItems": 43,
"maxItems": 43,
"description": "Raw Orchard receiving address for this contact: 11-byte diversifier followed by 32-byte diversified transmission key.",
"position": 4
}
},
"required": [
"accountReference",
"extendedPublicKey"
],
"additionalProperties": false
}
}
8 changes: 8 additions & 0 deletions packages/dashpay-contract/src/v3/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ use serde_json::Value;
// hold the first byte of each DIP-33 payment address to the address type, the
// check the profile data trigger made up to protocol version 14. Their descriptions say
// so in place of v2's mention of the data trigger.
//
// And it adds one type:
//
// - `privateContactRequest`, a private type of 589-byte entries: a contact request
// encrypted to the recipient and appended by `PrivateDocumentCreateFromShieldedPool`
// without an identity. Its properties describe the plaintext the recipient decrypts.
// At their largest they take 290 bytes (`accountReference` is an i64, as DashPay
// stores every integer), which with the envelope's 299 bytes fill an entry.

pub fn load_documents_schemas() -> Result<Value, Error> {
serde_json::from_str(include_str!("../../schema/v3/dashpay.schema.json"))
Expand Down
92 changes: 92 additions & 0 deletions packages/rs-dpp/src/system_data_contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1484,6 +1484,10 @@ mod dashpay_v3_tests {
DistinctFrom, DocumentPropertyReferenceTarget, DocumentPropertyType, DocumentTypeRef,
EncryptedFor, EncryptedForRecipient, EncryptionScheme,
};
use crate::document::serialization_traits::DocumentPlatformConversionMethodsV0;
use crate::document::DocumentV0;
use platform_value::{Identifier, Value};
use std::collections::BTreeMap;

/// DashPay v3, registered as the chain registers it, under full validation.
fn dashpay() -> DataContract {
Expand All @@ -1503,6 +1507,94 @@ mod dashpay_v3_tests {
/// from then on, so every property v1 or v2 declares must be stored exactly as
/// it was. DashPay keeps sized integer types off: an integer is an i64 whatever
/// its bounds, so the key indexes' new `maximum` changes nothing stored.
/// The bytes of an envelope around a private entry's payload, fixed by the private document
/// envelope (version 1): the commitment (32), the ephemeral key (33), the body's tag (16) and
/// the sender-recovery ciphertext (81) around the body, and the body's own header before the
/// payload (version, auth mode, sender id, sender key id, auth, recipient id, payload length:
/// 1 + 1 + 32 + 4 + 65 + 32 + 2).
const PRIVATE_ENTRY_WRAPPER_BYTES: usize = 162;
const PRIVATE_ENTRY_BODY_HEADER_BYTES: usize = 137;

/// The type's properties in the document property encoding, as a private entry's payload
/// carries them: each in position order, an optional one led by a presence byte.
fn payload(document_type: DocumentTypeRef, properties: &BTreeMap<String, Value>) -> Vec<u8> {
let mut bytes = vec![];
for (name, property) in document_type.properties() {
match properties.get(name) {
Some(value) => {
if !property.required {
bytes.push(1);
}
bytes.extend(
property
.property_type
.encode_value_ref_with_size(value, property.required)
.expect("the value encodes"),
);
}
None => bytes.push(0),
}
}
bytes
}

/// `privateContactRequest` is private, and its entries take the smallest size that holds its
/// largest payload in the envelope: every property at its largest, `accountReference` an
/// 8-byte integer as DashPay stores every integer, is 290 bytes, so 137 + 290 + 162 = 589.
/// The entry size is frozen with the contract, so this pins it to the properties.
#[test]
fn should_size_private_contact_requests_to_hold_their_largest_payload() {
let contract = dashpay();
let private_contact_request = document_type(&contract, "privateContactRequest");
let entry_size = private_contact_request
.private_document()
.expect("privateContactRequest is private")
.entry_size() as usize;
assert!(!private_contact_request.documents_mutable());
assert!(!private_contact_request.documents_can_be_deleted());

let largest = BTreeMap::from([
("accountReference".to_string(), Value::U32(u32::MAX)),
("extendedPublicKey".to_string(), Value::Bytes(vec![1; 69])),
("accountLabel".to_string(), Value::Text("a".repeat(63))),
("autoAcceptProof".to_string(), Value::Bytes(vec![2; 102])),
("shieldedAddress".to_string(), Value::Bytes(vec![3; 43])),
]);
let largest_payload = payload(private_contact_request, &largest);
assert_eq!(largest_payload.len(), 290);
assert_eq!(
PRIVATE_ENTRY_BODY_HEADER_BYTES + largest_payload.len() + PRIVATE_ENTRY_WRAPPER_BYTES,
entry_size
);

// The payload is the document's own property encoding: a document of the type
// serializes to its system fields followed by exactly these bytes.
let document = DocumentV0 {
id: Identifier::from([4; 32]),
owner_id: Identifier::from([5; 32]),
properties: largest,
..Default::default()
};
let serialized = document
.serialize(
private_contact_request,
&contract,
PlatformVersion::latest(),
)
.expect("the document serializes");
assert!(serialized.ends_with(&largest_payload));

// The smallest payload: the two required properties and three absent ones.
let smallest = BTreeMap::from([
("accountReference".to_string(), Value::U32(0)),
("extendedPublicKey".to_string(), Value::Bytes(vec![1; 69])),
]);
assert_eq!(
payload(private_contact_request, &smallest).len(),
8 + 69 + 3
);
}

#[test]
fn should_store_every_dashpay_v1_and_v2_property_as_it_was_stored() {
let v3 = dashpay();
Expand Down
44 changes: 25 additions & 19 deletions packages/rs-drive-abci/src/execution/check_tx/v0/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -657,7 +657,7 @@ mod tests {
166, 203, 222, 4, 64, 31, 215, 199, 149, 151, 190, 246, 251, 44,
]),
public_keys: BTreeMap::from([(1, key.clone())]),
balance: 25_000_000_000, // 0.25 Dash
balance: 27_000_000_000, // 0.27 Dash
revision: 0,
}
.into();
Expand Down Expand Up @@ -737,8 +737,9 @@ mod tests {
// 1.8 times its bytes, which are byte-billed, and checking its compiled size is
// charged per compiled byte
// and a document type's schema size is billed at least the values the
// repeated-key walk read; the DashPay v3 schema adds its declarations
24004855740
// repeated-key walk read; the DashPay v3 schema adds its declarations and the
// privateContactRequest type (one more document type registration fee)
26005728080
);

let check_result = platform
Expand Down Expand Up @@ -967,7 +968,7 @@ mod tests {
166, 203, 222, 4, 64, 31, 215, 199, 149, 151, 190, 246, 251, 44,
]),
public_keys: BTreeMap::from([(1, key.clone())]),
balance: 27_000_000_000,
balance: 29_000_000_000,
revision: 0,
}
.into();
Expand Down Expand Up @@ -1329,7 +1330,7 @@ mod tests {
166, 203, 222, 4, 64, 31, 215, 199, 149, 151, 190, 246, 251, 44,
]),
public_keys: BTreeMap::from([(1, key.clone())]),
balance: 25_000_000_000, // 0.25 Dash
balance: 27_000_000_000, // 0.27 Dash
revision: 0,
}
.into();
Expand Down Expand Up @@ -1412,8 +1413,9 @@ mod tests {
// 1.8 times its bytes, which are byte-billed, and checking its compiled size is
// charged per compiled byte
// and a document type's schema size is billed at least the values the
// repeated-key walk read; the DashPay v3 schema adds its declarations
24009711480
// repeated-key walk read; the DashPay v3 schema adds its declarations and the
// privateContactRequest type (one more document type registration fee)
26011456160
);

let check_result = platform
Expand Down Expand Up @@ -1497,8 +1499,9 @@ mod tests {
public_keys: BTreeMap::from([(1, key.clone())]),
// we have enough balance only for 1 insertion (this is where this test is different);
// from protocol version 15 one insertion stores the contract compiled, about 1.8
// times its bytes, and DashPay v3 adds its declarations, so it needs more
balance: 24_400_000_000,
// times its bytes, and DashPay v3 adds its declarations and a document type, so it
// needs more
balance: 26_400_000_000,
revision: 0,
}
.into();
Expand Down Expand Up @@ -1898,8 +1901,9 @@ mod tests {
// 1.8 times its bytes, which are byte-billed, and checking its compiled size is
// charged per compiled byte
// and a document type's schema size is billed at least the values the
// repeated-key walk read; the DashPay v3 schema adds its declarations
24004855740
// repeated-key walk read; the DashPay v3 schema adds its declarations and the
// privateContactRequest type (one more document type registration fee)
26005728080
);

platform
Expand Down Expand Up @@ -1997,8 +2001,9 @@ mod tests {
// about 1.8 times its bytes, which are byte-billed, and checking its compiled size
// is charged per compiled byte
// and a document type's schema size is billed at least the values the
// repeated-key walk read; the DashPay v3 schema adds its declarations
29005647760
// repeated-key walk read; the DashPay v3 schema adds its declarations and the
// privateContactRequest type (one more document type registration fee)
31006514040
);

let check_result = platform
Expand Down Expand Up @@ -2593,8 +2598,9 @@ mod tests {
// 1.8 times its bytes, which are byte-billed, and checking its compiled size is
// charged per compiled byte
// and a document type's schema size is billed at least the values the
// repeated-key walk read; the DashPay v3 schema adds its declarations
24004855740
// repeated-key walk read; the DashPay v3 schema adds its declarations and the
// privateContactRequest type (one more document type registration fee)
26005728080
);

platform
Expand Down Expand Up @@ -4395,7 +4401,7 @@ mod tests {
166, 203, 222, 4, 64, 31, 215, 199, 149, 151, 190, 246, 251, 44,
]),
public_keys: BTreeMap::from([(1, key.clone())]),
balance: 25_000_000_000, // 0.25 Dash
balance: 27_000_000_000, // 0.27 Dash
revision: 0,
}
.into();
Expand Down Expand Up @@ -4663,7 +4669,7 @@ mod tests {
let identity: Identity = IdentityV0 {
id: Identifier::new([7; 32]),
public_keys: BTreeMap::from([(1, key.clone())]),
balance: 25_000_000_000, // 0.25 Dash
balance: 27_000_000_000, // 0.27 Dash
revision: 0,
}
.into();
Expand Down Expand Up @@ -4791,7 +4797,7 @@ mod tests {
BasicError::SerializedObjectParsingError(_)
))
);
assert_eq!(balance, 25_000_000_000, "nothing is charged");
assert_eq!(balance, 27_000_000_000, "nothing is charged");
}
}

Expand All @@ -4813,7 +4819,7 @@ mod tests {
execution_result,
StateTransitionExecutionResult::SuccessfulExecution { .. }
);
assert!(balance < 25_000_000_000, "the create is paid");
assert!(balance < 27_000_000_000, "the create is paid");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2709,6 +2709,23 @@ mod tests {
"v3 must read the v2 {document_type_name} back unchanged"
);
}

// v3's new private type gets its store with the upgrade, as a contract update adding a
// private type creates one: 589-byte entries, 512 to a chunk.
assert_eq!(
platform
.drive
.private_document_store_chunk_power_taking_entries(
SystemDataContract::Dashpay.id().as_bytes(),
"privateContactRequest",
589,
None,
platform_version_15,
)
.expect("expected to read the store"),
Some(9),
"the upgrade creates the privateContactRequest store"
);
}

// test_transition_to_version_9 removed: requires prior state from versions 4-8
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ mod deletion_tests {
// genesis contracts tree, increasing the bytes billed when reading that tree
// (protocol 14 only). The decision proposals and token market contracts add two more
// at protocol 15, which reshape the tree so the contract read is billed fewer bytes (-47_280).
1770160,
// DashPay v3's privateContactRequest type makes its sibling node larger (+2_860).
1773020,
)
.await;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,8 @@ mod replacement_tests {
// genesis contracts tree, increasing the bytes billed when reading that tree
// (protocol 14 only). The decision proposals and token market contracts add two more
// at protocol 15, which reshape the tree so the contract read is billed fewer bytes (-47_280).
1498860,
// DashPay v3's privateContactRequest type makes its sibling node larger (+2_860).
1501720,
)
.await;
}
Expand Down
Loading
Loading