1use crate::address_funds::PlatformAddress;
11use crate::fee::Credits;
12use crate::identity::identity_public_key::contract_bounds::ContractBounds;
13use crate::identity::state_transition::asset_lock_proof::AssetLockProof;
14use crate::prelude::AddressNonce;
15use crate::shielded::{serialized_actions_digest, SerializedAction};
16use crate::state_transition::batch_transition::batched_transition::token_transition_action_type::TokenTransitionActionType;
17use crate::state_transition::public_key_in_creation::accessors::IdentityPublicKeyInCreationV0Getters;
18use crate::state_transition::public_key_in_creation::IdentityPublicKeyInCreation;
19use crate::withdrawal::Pooling;
20use crate::ProtocolError;
21use platform_version::version::PlatformVersion;
22use sha2::{Digest, Sha256};
23use std::collections::BTreeMap;
24
25const SIGHASH_DOMAIN: &[u8] = b"DashPlatformSighash";
27
28pub const TOKEN_SHIELDED_TRANSFER_WITH_SHIELDED_FEE_TYPE: u8 = 26;
31pub const TOKEN_UNSHIELD_WITH_SHIELDED_FEE_TYPE: u8 = 27;
33pub const TOKEN_PURCHASE_FROM_SHIELDED_POOL_TYPE: u8 = 28;
35
36pub const TOKEN_SHIELD_BUNDLE_TAG: u8 = 0x80;
44pub const TOKEN_MINT_TO_POOL_BUNDLE_TAG: u8 = 0x81;
46pub const TOKEN_CLAIM_TO_POOL_BUNDLE_TAG: u8 = 0x82;
48pub const TOKEN_DIRECT_PURCHASE_TO_POOL_BUNDLE_TAG: u8 = 0x83;
50
51pub const SHIELD_BUNDLE_TAG: u8 = 0x84;
57pub const SHIELD_FROM_IDENTITY_BUNDLE_TAG: u8 = 0x85;
59pub const SHIELD_FROM_ASSET_LOCK_BUNDLE_TAG: u8 = 0x86;
61
62pub fn compute_platform_sighash(bundle_commitment: &[u8; 32], extra_data: &[u8]) -> [u8; 32] {
85 let mut hasher = Sha256::new();
86 hasher.update(SIGHASH_DOMAIN);
87 hasher.update(bundle_commitment);
88 hasher.update(extra_data);
89 hasher.finalize().into()
90}
91
92pub fn shielded_withdrawal_extra_sighash_data(
119 output_script: &[u8],
120 unshielding_amount: u64,
121 core_fee_per_byte: u32,
122 pooling: Pooling,
123 platform_version: &PlatformVersion,
124) -> Result<Vec<u8>, ProtocolError> {
125 match platform_version.dpp.methods.shielded_extra_sighash_data {
126 0 => Ok(shielded_withdrawal_extra_sighash_data_v0(
127 output_script,
128 unshielding_amount,
129 core_fee_per_byte,
130 pooling,
131 )),
132 version => Err(ProtocolError::UnknownVersionMismatch {
133 method: "shielded_withdrawal_extra_sighash_data".to_string(),
134 known_versions: vec![0],
135 received: version,
136 }),
137 }
138}
139
140pub fn shielded_withdrawal_extra_sighash_data_v0(
143 output_script: &[u8],
144 unshielding_amount: u64,
145 core_fee_per_byte: u32,
146 pooling: Pooling,
147) -> Vec<u8> {
148 let mut data = Vec::with_capacity(output_script.len() + 8 + 4 + 1);
149 data.extend_from_slice(output_script);
150 data.extend_from_slice(&unshielding_amount.to_le_bytes());
151 data.extend_from_slice(&core_fee_per_byte.to_le_bytes());
152 data.push(pooling as u8);
153 data
154}
155
156pub fn unshield_extra_sighash_data(
164 output_address: &[u8],
165 unshielding_amount: u64,
166 platform_version: &PlatformVersion,
167) -> Result<Vec<u8>, ProtocolError> {
168 match platform_version.dpp.methods.shielded_extra_sighash_data {
169 0 => Ok(unshield_extra_sighash_data_v0(
170 output_address,
171 unshielding_amount,
172 )),
173 version => Err(ProtocolError::UnknownVersionMismatch {
174 method: "unshield_extra_sighash_data".to_string(),
175 known_versions: vec![0],
176 received: version,
177 }),
178 }
179}
180
181pub fn unshield_extra_sighash_data_v0(output_address: &[u8], unshielding_amount: u64) -> Vec<u8> {
184 let mut data = Vec::with_capacity(output_address.len() + 8);
185 data.extend_from_slice(output_address);
186 data.extend_from_slice(&unshielding_amount.to_le_bytes());
187 data
188}
189
190pub fn identity_top_up_from_shielded_extra_sighash_data(
199 identity_id: &[u8; 32],
200 top_up_amount: u64,
201 platform_version: &PlatformVersion,
202) -> Result<Vec<u8>, ProtocolError> {
203 match platform_version.dpp.methods.shielded_extra_sighash_data {
204 0 => Ok(identity_top_up_from_shielded_extra_sighash_data_v0(
205 identity_id,
206 top_up_amount,
207 )),
208 version => Err(ProtocolError::UnknownVersionMismatch {
209 method: "identity_top_up_from_shielded_extra_sighash_data".to_string(),
210 known_versions: vec![0],
211 received: version,
212 }),
213 }
214}
215
216pub fn identity_top_up_from_shielded_extra_sighash_data_v0(
219 identity_id: &[u8; 32],
220 top_up_amount: u64,
221) -> Vec<u8> {
222 let mut data = Vec::with_capacity(32 + 8);
223 data.extend_from_slice(identity_id);
224 data.extend_from_slice(&top_up_amount.to_le_bytes());
225 data
226}
227
228pub fn identity_create_from_shielded_extra_sighash_data(
264 identity_id: &[u8; 32],
265 denomination: u64,
266 send_to_address_on_creation_failure: &PlatformAddress,
267 public_keys: &[IdentityPublicKeyInCreation],
268 platform_version: &PlatformVersion,
269) -> Result<Vec<u8>, ProtocolError> {
270 match platform_version.dpp.methods.shielded_extra_sighash_data {
271 0 => Ok(identity_create_from_shielded_extra_sighash_data_v0(
272 identity_id,
273 denomination,
274 send_to_address_on_creation_failure,
275 public_keys,
276 )),
277 version => Err(ProtocolError::UnknownVersionMismatch {
278 method: "identity_create_from_shielded_extra_sighash_data".to_string(),
279 known_versions: vec![0],
280 received: version,
281 }),
282 }
283}
284
285pub fn identity_create_from_shielded_extra_sighash_data_v0(
289 identity_id: &[u8; 32],
290 denomination: u64,
291 send_to_address_on_creation_failure: &PlatformAddress,
292 public_keys: &[IdentityPublicKeyInCreation],
293) -> Vec<u8> {
294 let mut data = Vec::with_capacity(32 + 8 + 21 + 2 + public_keys.len() * 44);
295 data.extend_from_slice(identity_id);
296 data.extend_from_slice(&denomination.to_le_bytes());
297 match send_to_address_on_creation_failure {
300 PlatformAddress::P2pkh(hash) => {
301 data.push(0u8);
302 data.extend_from_slice(hash);
303 }
304 PlatformAddress::P2sh(hash) => {
305 data.push(1u8);
306 data.extend_from_slice(hash);
307 }
308 }
309 data.extend_from_slice(&(public_keys.len() as u16).to_le_bytes());
310 for key in public_keys {
311 data.extend_from_slice(&key.id().to_le_bytes());
312 data.push(key.purpose() as u8);
313 data.push(key.security_level() as u8);
314 data.push(key.key_type() as u8);
315 let key_data = key.data().as_slice();
316 data.extend_from_slice(&(key_data.len() as u16).to_le_bytes());
317 data.extend_from_slice(key_data);
318 data.push(key.read_only() as u8);
324 match key.contract_bounds() {
325 None => data.push(0u8),
326 Some(ContractBounds::SingleContract { id }) => {
327 data.push(1u8);
328 data.extend_from_slice(id.as_bytes());
329 }
330 Some(ContractBounds::SingleContractDocumentType {
331 id,
332 document_type_name,
333 }) => {
334 data.push(2u8);
335 data.extend_from_slice(id.as_bytes());
336 let name = document_type_name.as_bytes();
337 data.extend_from_slice(&(name.len() as u16).to_le_bytes());
338 data.extend_from_slice(name);
339 }
340 Some(ContractBounds::ContractGroup { id }) => {
341 data.push(3u8);
343 data.extend_from_slice(id.as_bytes());
344 }
345 }
346 }
347 data
348}
349
350pub fn token_unshield_extra_sighash_data(
361 token_id: &[u8; 32],
362 owner_id: &[u8; 32],
363 recipient_id: &[u8; 32],
364 amount: u64,
365 platform_version: &PlatformVersion,
366) -> Result<Vec<u8>, ProtocolError> {
367 match platform_version.dpp.methods.shielded_extra_sighash_data {
368 0 => Ok(token_unshield_extra_sighash_data_v0(
369 token_id,
370 owner_id,
371 recipient_id,
372 amount,
373 )),
374 version => Err(ProtocolError::UnknownVersionMismatch {
375 method: "token_unshield_extra_sighash_data".to_string(),
376 known_versions: vec![0],
377 received: version,
378 }),
379 }
380}
381
382pub fn token_unshield_extra_sighash_data_v0(
385 token_id: &[u8; 32],
386 owner_id: &[u8; 32],
387 recipient_id: &[u8; 32],
388 amount: u64,
389) -> Vec<u8> {
390 let mut data = Vec::with_capacity(32 + 32 + 32 + 8);
391 data.extend_from_slice(token_id);
392 data.extend_from_slice(owner_id);
393 data.extend_from_slice(recipient_id);
394 data.extend_from_slice(&amount.to_le_bytes());
395 data
396}
397
398pub fn token_burn_from_pool_extra_sighash_data(
407 token_id: &[u8; 32],
408 burner_id: &[u8; 32],
409 amount: u64,
410 platform_version: &PlatformVersion,
411) -> Result<Vec<u8>, ProtocolError> {
412 match platform_version.dpp.methods.shielded_extra_sighash_data {
413 0 => Ok(token_burn_from_pool_extra_sighash_data_v0(
414 token_id, burner_id, amount,
415 )),
416 version => Err(ProtocolError::UnknownVersionMismatch {
417 method: "token_burn_from_pool_extra_sighash_data".to_string(),
418 known_versions: vec![0],
419 received: version,
420 }),
421 }
422}
423
424pub fn token_burn_from_pool_extra_sighash_data_v0(
426 token_id: &[u8; 32],
427 burner_id: &[u8; 32],
428 amount: u64,
429) -> Vec<u8> {
430 let mut data = Vec::with_capacity(32 + 32 + 8);
431 data.extend_from_slice(token_id);
432 data.extend_from_slice(burner_id);
433 data.extend_from_slice(&amount.to_le_bytes());
434 data
435}
436
437pub fn document_token_payment_extra_sighash_data(
442 token_id: &[u8; 32],
443 owner_id: &[u8; 32],
444 data_contract_id: &[u8; 32],
445 document_id: &[u8; 32],
446 amount: u64,
447 platform_version: &PlatformVersion,
448) -> Result<Vec<u8>, ProtocolError> {
449 match platform_version.dpp.methods.shielded_extra_sighash_data {
450 0 => Ok(document_token_payment_extra_sighash_data_v0(
451 token_id,
452 owner_id,
453 data_contract_id,
454 document_id,
455 amount,
456 )),
457 version => Err(ProtocolError::UnknownVersionMismatch {
458 method: "document_token_payment_extra_sighash_data".to_string(),
459 known_versions: vec![0],
460 received: version,
461 }),
462 }
463}
464
465pub fn document_token_payment_extra_sighash_data_v0(
469 token_id: &[u8; 32],
470 owner_id: &[u8; 32],
471 data_contract_id: &[u8; 32],
472 document_id: &[u8; 32],
473 amount: u64,
474) -> Vec<u8> {
475 let mut data = Vec::with_capacity(32 * 4 + 8);
476 data.extend_from_slice(token_id);
477 data.extend_from_slice(owner_id);
478 data.extend_from_slice(data_contract_id);
479 data.extend_from_slice(document_id);
480 data.extend_from_slice(&amount.to_le_bytes());
481 data
482}
483
484pub fn token_shielded_transfer_with_shielded_fee_extra_sighash_data(
488 token_id: &[u8; 32],
489 platform_version: &PlatformVersion,
490) -> Result<Vec<u8>, ProtocolError> {
491 match platform_version.dpp.methods.shielded_extra_sighash_data {
492 0 => Ok(token_shielded_transfer_with_shielded_fee_extra_sighash_data_v0(token_id)),
493 version => Err(ProtocolError::UnknownVersionMismatch {
494 method: "token_shielded_transfer_with_shielded_fee_extra_sighash_data".to_string(),
495 known_versions: vec![0],
496 received: version,
497 }),
498 }
499}
500
501pub fn token_shielded_transfer_with_shielded_fee_extra_sighash_data_v0(
503 token_id: &[u8; 32],
504) -> Vec<u8> {
505 let mut data = Vec::with_capacity(1 + 32);
506 data.push(TOKEN_SHIELDED_TRANSFER_WITH_SHIELDED_FEE_TYPE);
507 data.extend_from_slice(token_id);
508 data
509}
510
511pub fn token_unshield_with_shielded_fee_extra_sighash_data(
515 token_id: &[u8; 32],
516 recipient_id: &[u8; 32],
517 amount: u64,
518 platform_version: &PlatformVersion,
519) -> Result<Vec<u8>, ProtocolError> {
520 match platform_version.dpp.methods.shielded_extra_sighash_data {
521 0 => Ok(token_unshield_with_shielded_fee_extra_sighash_data_v0(
522 token_id,
523 recipient_id,
524 amount,
525 )),
526 version => Err(ProtocolError::UnknownVersionMismatch {
527 method: "token_unshield_with_shielded_fee_extra_sighash_data".to_string(),
528 known_versions: vec![0],
529 received: version,
530 }),
531 }
532}
533
534pub fn token_unshield_with_shielded_fee_extra_sighash_data_v0(
537 token_id: &[u8; 32],
538 recipient_id: &[u8; 32],
539 amount: u64,
540) -> Vec<u8> {
541 let mut data = Vec::with_capacity(1 + 32 + 32 + 8);
542 data.push(TOKEN_UNSHIELD_WITH_SHIELDED_FEE_TYPE);
543 data.extend_from_slice(token_id);
544 data.extend_from_slice(recipient_id);
545 data.extend_from_slice(&amount.to_le_bytes());
546 data
547}
548
549pub fn token_purchase_from_shielded_pool_extra_sighash_data(
552 token_id: &[u8; 32],
553 token_count: u64,
554 total_agreed_price: u64,
555 platform_version: &PlatformVersion,
556) -> Result<Vec<u8>, ProtocolError> {
557 match platform_version.dpp.methods.shielded_extra_sighash_data {
558 0 => Ok(token_purchase_from_shielded_pool_extra_sighash_data_v0(
559 token_id,
560 token_count,
561 total_agreed_price,
562 )),
563 version => Err(ProtocolError::UnknownVersionMismatch {
564 method: "token_purchase_from_shielded_pool_extra_sighash_data".to_string(),
565 known_versions: vec![0],
566 received: version,
567 }),
568 }
569}
570
571pub fn token_purchase_from_shielded_pool_extra_sighash_data_v0(
574 token_id: &[u8; 32],
575 token_count: u64,
576 total_agreed_price: u64,
577) -> Vec<u8> {
578 let mut data = Vec::with_capacity(1 + 32 + 8 + 8);
579 data.push(TOKEN_PURCHASE_FROM_SHIELDED_POOL_TYPE);
580 data.extend_from_slice(token_id);
581 data.extend_from_slice(&token_count.to_le_bytes());
582 data.extend_from_slice(&total_agreed_price.to_le_bytes());
583 data
584}
585
586pub fn token_pool_fee_bundle_extra_sighash_data(
590 state_transition_type: u8,
591 token_id: &[u8; 32],
592 token_actions: &[SerializedAction],
593 platform_version: &PlatformVersion,
594) -> Result<Vec<u8>, ProtocolError> {
595 match platform_version.dpp.methods.shielded_extra_sighash_data {
596 0 => Ok(token_pool_fee_bundle_extra_sighash_data_v0(
597 state_transition_type,
598 token_id,
599 &serialized_actions_digest(token_actions),
600 )),
601 version => Err(ProtocolError::UnknownVersionMismatch {
602 method: "token_pool_fee_bundle_extra_sighash_data".to_string(),
603 known_versions: vec![0],
604 received: version,
605 }),
606 }
607}
608
609pub fn token_pool_fee_bundle_extra_sighash_data_v0(
612 state_transition_type: u8,
613 token_id: &[u8; 32],
614 token_actions_digest: &[u8; 32],
615) -> Vec<u8> {
616 let mut data = Vec::with_capacity(1 + 32 + 32);
617 data.push(state_transition_type);
618 data.extend_from_slice(token_id);
619 data.extend_from_slice(token_actions_digest);
620 data
621}
622
623pub fn token_shielded_transfer_extra_sighash_data(
630 token_id: &[u8; 32],
631 owner_id: &[u8; 32],
632 platform_version: &PlatformVersion,
633) -> Result<Vec<u8>, ProtocolError> {
634 match platform_version.dpp.methods.shielded_extra_sighash_data {
635 0 => Ok(token_shielded_transfer_extra_sighash_data_v0(
636 token_id, owner_id,
637 )),
638 version => Err(ProtocolError::UnknownVersionMismatch {
639 method: "token_shielded_transfer_extra_sighash_data".to_string(),
640 known_versions: vec![0],
641 received: version,
642 }),
643 }
644}
645
646pub fn token_shielded_transfer_extra_sighash_data_v0(
649 token_id: &[u8; 32],
650 owner_id: &[u8; 32],
651) -> Vec<u8> {
652 let mut data = Vec::with_capacity(64);
653 data.extend_from_slice(token_id);
654 data.extend_from_slice(owner_id);
655 data
656}
657
658pub fn token_pool_output_only_extra_sighash_data(
696 action_type: TokenTransitionActionType,
697 token_id: &[u8; 32],
698 owner_id: &[u8; 32],
699 platform_version: &PlatformVersion,
700) -> Result<Vec<u8>, ProtocolError> {
701 match platform_version.dpp.methods.shielded_extra_sighash_data {
702 0 => Ok(token_pool_output_only_extra_sighash_data_v0(
703 outputs_only_bundle_tag_v0(action_type)?,
704 token_id,
705 owner_id,
706 )),
707 version => Err(ProtocolError::UnknownVersionMismatch {
708 method: "token_pool_output_only_extra_sighash_data".to_string(),
709 known_versions: vec![0],
710 received: version,
711 }),
712 }
713}
714
715fn outputs_only_bundle_tag_v0(action_type: TokenTransitionActionType) -> Result<u8, ProtocolError> {
727 match action_type {
728 TokenTransitionActionType::Shield => Ok(TOKEN_SHIELD_BUNDLE_TAG),
729 TokenTransitionActionType::MintToPool => Ok(TOKEN_MINT_TO_POOL_BUNDLE_TAG),
730 TokenTransitionActionType::ClaimToPool => Ok(TOKEN_CLAIM_TO_POOL_BUNDLE_TAG),
731 TokenTransitionActionType::DirectPurchaseToPool => {
732 Ok(TOKEN_DIRECT_PURCHASE_TO_POOL_BUNDLE_TAG)
733 }
734 other @ (TokenTransitionActionType::Burn
735 | TokenTransitionActionType::Mint
736 | TokenTransitionActionType::Transfer
737 | TokenTransitionActionType::Freeze
738 | TokenTransitionActionType::Unfreeze
739 | TokenTransitionActionType::DestroyFrozenFunds
740 | TokenTransitionActionType::Claim
741 | TokenTransitionActionType::EmergencyAction
742 | TokenTransitionActionType::ConfigUpdate
743 | TokenTransitionActionType::DirectPurchase
744 | TokenTransitionActionType::SetPriceForDirectPurchase
745 | TokenTransitionActionType::BurnFromPool
746 | TokenTransitionActionType::Unshield
747 | TokenTransitionActionType::ShieldedTransfer) => {
748 Err(ProtocolError::InvalidStateTransitionType(format!(
749 "{other} is not an outputs-only token pool transition and has no bundle tag"
750 )))
751 }
752 }
753}
754
755pub fn token_pool_output_only_extra_sighash_data_v0(
758 bundle_tag: u8,
759 token_id: &[u8; 32],
760 owner_id: &[u8; 32],
761) -> Vec<u8> {
762 let mut data = Vec::with_capacity(1 + 32 + 32);
763 data.push(bundle_tag);
764 data.extend_from_slice(token_id);
765 data.extend_from_slice(owner_id);
766 data
767}
768
769pub fn shield_extra_sighash_data(
792 inputs: &BTreeMap<PlatformAddress, (AddressNonce, Credits)>,
793 platform_version: &PlatformVersion,
794) -> Result<Vec<u8>, ProtocolError> {
795 match platform_version.dpp.methods.credit_pool_bundle_binding {
796 None => Ok(Vec::new()),
797 Some(0) => Ok(credit_pool_output_only_extra_sighash_data_v0(
798 SHIELD_BUNDLE_TAG,
799 &shield_funding_digest_v0(inputs),
800 )),
801 Some(version) => Err(ProtocolError::UnknownVersionMismatch {
802 method: "shield_extra_sighash_data".to_string(),
803 known_versions: vec![0],
804 received: version,
805 }),
806 }
807}
808
809fn shield_funding_digest_v0(
812 inputs: &BTreeMap<PlatformAddress, (AddressNonce, Credits)>,
813) -> [u8; 32] {
814 let mut hasher = Sha256::new();
815 for address in inputs.keys() {
816 hasher.update(address.to_bytes());
817 }
818 hasher.finalize().into()
819}
820
821pub fn shield_from_identity_extra_sighash_data(
833 identity_id: &[u8; 32],
834 platform_version: &PlatformVersion,
835) -> Result<Vec<u8>, ProtocolError> {
836 match platform_version.dpp.methods.credit_pool_bundle_binding {
837 None => Ok(Vec::new()),
838 Some(0) => Ok(credit_pool_output_only_extra_sighash_data_v0(
839 SHIELD_FROM_IDENTITY_BUNDLE_TAG,
840 identity_id,
841 )),
842 Some(version) => Err(ProtocolError::UnknownVersionMismatch {
843 method: "shield_from_identity_extra_sighash_data".to_string(),
844 known_versions: vec![0],
845 received: version,
846 }),
847 }
848}
849
850pub fn shield_from_asset_lock_extra_sighash_data(
867 asset_lock_proof: &AssetLockProof,
868 platform_version: &PlatformVersion,
869) -> Result<Vec<u8>, ProtocolError> {
870 match platform_version.dpp.methods.credit_pool_bundle_binding {
871 None => Ok(Vec::new()),
872 Some(0) => Ok(credit_pool_output_only_extra_sighash_data_v0(
873 SHIELD_FROM_ASSET_LOCK_BUNDLE_TAG,
874 &asset_lock_proof.create_identifier()?.to_buffer(),
875 )),
876 Some(version) => Err(ProtocolError::UnknownVersionMismatch {
877 method: "shield_from_asset_lock_extra_sighash_data".to_string(),
878 known_versions: vec![0],
879 received: version,
880 }),
881 }
882}
883
884pub fn credit_pool_output_only_extra_sighash_data_v0(bundle_tag: u8, owner: &[u8; 32]) -> Vec<u8> {
910 let mut data = Vec::with_capacity(1 + 32);
911 data.push(bundle_tag);
912 data.extend_from_slice(owner);
913 data
914}
915
916#[cfg(test)]
917mod tests {
918 use super::*;
919 use crate::identity::core_script::CoreScript;
920 use crate::state_transition::StateTransitionType;
921 use crate::withdrawal::Pooling;
922 use crate::shielded::shielded_withdrawal_extra_sighash_data_v0 as shielded_withdrawal_extra_sighash_data;
925 use crate::shielded::unshield_extra_sighash_data_v0 as unshield_extra_sighash_data;
926
927 #[test]
928 fn withdrawal_sighash_data_binds_core_fee_per_byte() {
929 let script = CoreScript::new_p2pkh([1u8; 20]);
930 let a = shielded_withdrawal_extra_sighash_data(script.as_bytes(), 1000, 1, Pooling::Never);
931 let b = shielded_withdrawal_extra_sighash_data(script.as_bytes(), 1000, 2, Pooling::Never);
932 assert_ne!(
933 a, b,
934 "changing core_fee_per_byte must change the sighash preimage"
935 );
936 }
937
938 #[test]
939 fn withdrawal_sighash_data_binds_pooling() {
940 let script = CoreScript::new_p2pkh([1u8; 20]);
944 let a = shielded_withdrawal_extra_sighash_data(script.as_bytes(), 1000, 1, Pooling::Never);
945 let b = shielded_withdrawal_extra_sighash_data(
946 script.as_bytes(),
947 1000,
948 1,
949 Pooling::IfAvailable,
950 );
951 assert_ne!(a, b, "changing pooling must change the sighash preimage");
952 }
953
954 #[test]
955 fn withdrawal_sighash_data_layout() {
956 let d = shielded_withdrawal_extra_sighash_data(&[0xAA, 0xBB], 1, 2, Pooling::Never);
958 assert_eq!(d.len(), 2 + 8 + 4 + 1);
959 assert_eq!(&d[0..2], &[0xAA, 0xBB]);
960 assert_eq!(&d[2..10], &1u64.to_le_bytes());
961 assert_eq!(&d[10..14], &2u32.to_le_bytes());
962 assert_eq!(d[14], Pooling::Never as u8);
963 }
964
965 #[test]
966 fn token_unshield_sighash_data_layout() {
967 use crate::shielded::token_unshield_extra_sighash_data_v0;
968 let d =
970 token_unshield_extra_sighash_data_v0(&[0xAAu8; 32], &[0xBBu8; 32], &[0xCCu8; 32], 7);
971 assert_eq!(d.len(), 32 + 32 + 32 + 8);
972 assert_eq!(&d[0..32], &[0xAAu8; 32]);
973 assert_eq!(&d[32..64], &[0xBBu8; 32]);
974 assert_eq!(&d[64..96], &[0xCCu8; 32]);
975 assert_eq!(&d[96..104], &7u64.to_le_bytes());
976 assert_ne!(
978 d,
979 token_unshield_extra_sighash_data_v0(&[0xADu8; 32], &[0xBBu8; 32], &[0xCCu8; 32], 7)
980 );
981 assert_ne!(
982 d,
983 token_unshield_extra_sighash_data_v0(&[0xAAu8; 32], &[0xB0u8; 32], &[0xCCu8; 32], 7)
984 );
985 assert_ne!(
986 d,
987 token_unshield_extra_sighash_data_v0(&[0xAAu8; 32], &[0xBBu8; 32], &[0xC0u8; 32], 7)
988 );
989 assert_ne!(
990 d,
991 token_unshield_extra_sighash_data_v0(&[0xAAu8; 32], &[0xBBu8; 32], &[0xCCu8; 32], 8)
992 );
993 }
994
995 #[test]
996 fn token_shielded_transfer_sighash_data_layout() {
997 use crate::shielded::token_shielded_transfer_extra_sighash_data_v0;
998 let d = token_shielded_transfer_extra_sighash_data_v0(&[0x11u8; 32], &[0x22u8; 32]);
1000 assert_eq!(d.len(), 64);
1001 assert_eq!(&d[0..32], &[0x11u8; 32]);
1002 assert_eq!(&d[32..64], &[0x22u8; 32]);
1003 assert_ne!(
1004 d,
1005 token_shielded_transfer_extra_sighash_data_v0(&[0x12u8; 32], &[0x22u8; 32])
1006 );
1007 assert_ne!(
1008 d,
1009 token_shielded_transfer_extra_sighash_data_v0(&[0x11u8; 32], &[0x23u8; 32])
1010 );
1011 }
1012
1013 #[test]
1014 fn unshield_sighash_data_layout() {
1015 let d = unshield_extra_sighash_data(&[0xAA, 0xBB, 0xCC], 5);
1017 assert_eq!(d.len(), 3 + 8);
1018 assert_eq!(&d[0..3], &[0xAA, 0xBB, 0xCC]);
1019 assert_eq!(&d[3..11], &5u64.to_le_bytes());
1020 }
1021
1022 mod identity_create_sighash {
1023 use super::*;
1024 use crate::identity::{KeyType, Purpose, SecurityLevel};
1026 use crate::shielded::identity_create_from_shielded_extra_sighash_data_v0 as identity_create_from_shielded_extra_sighash_data;
1027 use crate::state_transition::public_key_in_creation::v0::IdentityPublicKeyInCreationV0;
1028 use crate::state_transition::public_key_in_creation::IdentityPublicKeyInCreation;
1029 use platform_value::BinaryData;
1030
1031 fn mk_key(id: u32, data_byte: u8) -> IdentityPublicKeyInCreation {
1032 IdentityPublicKeyInCreation::V0(IdentityPublicKeyInCreationV0 {
1033 id,
1034 key_type: KeyType::ECDSA_SECP256K1,
1035 purpose: Purpose::AUTHENTICATION,
1036 security_level: SecurityLevel::MASTER,
1037 contract_bounds: None,
1038 read_only: false,
1039 data: BinaryData::new(vec![data_byte; 33]),
1040 signature: BinaryData::new(vec![]),
1041 })
1042 }
1043
1044 #[test]
1045 fn layout_is_length_prefixed() {
1046 let id = [0x11u8; 32];
1051 let keys = vec![mk_key(7, 0xAB)];
1052 let fallback = PlatformAddress::P2pkh([0x5Cu8; 20]);
1053 let d = identity_create_from_shielded_extra_sighash_data(
1054 &id,
1055 10_000_000_000,
1056 &fallback,
1057 &keys,
1058 );
1059 assert_eq!(&d[0..32], &id);
1060 assert_eq!(&d[32..40], &10_000_000_000u64.to_le_bytes());
1061 assert_eq!(d[40], 0u8, "fallback address P2pkh tag");
1063 assert_eq!(&d[41..61], &[0x5Cu8; 20], "fallback address hash");
1064 assert_eq!(&d[61..63], &1u16.to_le_bytes());
1065 assert_eq!(&d[63..67], &7u32.to_le_bytes());
1066 assert_eq!(d[67], Purpose::AUTHENTICATION as u8);
1067 assert_eq!(d[68], SecurityLevel::MASTER as u8);
1068 assert_eq!(d[69], KeyType::ECDSA_SECP256K1 as u8);
1069 assert_eq!(&d[70..72], &33u16.to_le_bytes());
1070 assert_eq!(&d[72..105], &[0xAB; 33]);
1071 assert_eq!(d[105], 0u8, "read_only=false");
1072 assert_eq!(d[106], 0u8, "contract_bounds=None tag");
1073 assert_eq!(d.len(), 32 + 8 + 21 + 2 + (4 + 1 + 1 + 1 + 2 + 33 + 1 + 1));
1074 }
1075
1076 #[test]
1077 fn binds_identity_id_denomination_and_keys() {
1078 let id_a = [0x11u8; 32];
1079 let id_b = [0x22u8; 32];
1080 let keys = vec![mk_key(0, 0xAA)];
1081 let fallback = PlatformAddress::P2pkh([0x01u8; 20]);
1082 let base = identity_create_from_shielded_extra_sighash_data(
1083 &id_a,
1084 10_000_000_000,
1085 &fallback,
1086 &keys,
1087 );
1088
1089 assert_ne!(
1091 base,
1092 identity_create_from_shielded_extra_sighash_data(
1093 &id_b,
1094 10_000_000_000,
1095 &fallback,
1096 &keys
1097 ),
1098 "identity id must be bound"
1099 );
1100 assert_ne!(
1102 base,
1103 identity_create_from_shielded_extra_sighash_data(
1104 &id_a,
1105 25_000_000_000,
1106 &fallback,
1107 &keys
1108 ),
1109 "denomination must be bound"
1110 );
1111 assert_ne!(
1115 base,
1116 identity_create_from_shielded_extra_sighash_data(
1117 &id_a,
1118 10_000_000_000,
1119 &PlatformAddress::P2pkh([0x02u8; 20]),
1120 &keys
1121 ),
1122 "fallback failure address hash must be bound"
1123 );
1124 assert_ne!(
1127 base,
1128 identity_create_from_shielded_extra_sighash_data(
1129 &id_a,
1130 10_000_000_000,
1131 &PlatformAddress::P2sh([0x01u8; 20]),
1132 &keys
1133 ),
1134 "fallback failure address type tag must be bound"
1135 );
1136 assert_ne!(
1138 base,
1139 identity_create_from_shielded_extra_sighash_data(
1140 &id_a,
1141 10_000_000_000,
1142 &fallback,
1143 &[mk_key(0, 0xBB)]
1144 ),
1145 "key data must be bound"
1146 );
1147 assert_ne!(
1149 base,
1150 identity_create_from_shielded_extra_sighash_data(
1151 &id_a,
1152 10_000_000_000,
1153 &fallback,
1154 &[mk_key(0, 0xAA), mk_key(1, 0xCC)]
1155 ),
1156 "the full key set must be bound"
1157 );
1158 }
1159
1160 #[test]
1161 fn binds_read_only_and_contract_bounds() {
1162 use crate::identity::identity_public_key::contract_bounds::ContractBounds;
1163 use crate::state_transition::public_key_in_creation::accessors::IdentityPublicKeyInCreationV0Setters;
1164 let id = [0x11u8; 32];
1165 let fallback = PlatformAddress::P2pkh([0x01u8; 20]);
1166 let base = identity_create_from_shielded_extra_sighash_data(
1167 &id,
1168 10_000_000_000,
1169 &fallback,
1170 &[mk_key(0, 0xAA)],
1171 );
1172
1173 let mut ro_key = mk_key(0, 0xAA);
1175 ro_key.set_read_only(true);
1176 assert_ne!(
1177 base,
1178 identity_create_from_shielded_extra_sighash_data(
1179 &id,
1180 10_000_000_000,
1181 &fallback,
1182 &[ro_key]
1183 ),
1184 "read_only must be bound"
1185 );
1186
1187 let mut cb_key = mk_key(0, 0xAA);
1189 cb_key.set_contract_bounds(Some(ContractBounds::SingleContract {
1190 id: platform_value::Identifier::new([0x33; 32]),
1191 }));
1192 assert_ne!(
1193 base,
1194 identity_create_from_shielded_extra_sighash_data(
1195 &id,
1196 10_000_000_000,
1197 &fallback,
1198 &[cb_key]
1199 ),
1200 "contract_bounds must be bound"
1201 );
1202 }
1203
1204 #[test]
1205 fn should_encode_the_reserved_contract_group_tag_at_the_end_of_the_key() {
1206 use crate::identity::identity_public_key::contract_bounds::ContractBounds;
1207 use crate::state_transition::public_key_in_creation::accessors::IdentityPublicKeyInCreationV0Setters;
1208 let mut key = mk_key(0, 0xAA);
1211 key.set_contract_bounds(Some(ContractBounds::ContractGroup {
1212 id: platform_value::Identifier::new([0x44; 32]),
1213 }));
1214 let data = identity_create_from_shielded_extra_sighash_data(
1215 &[0x11u8; 32],
1216 10_000_000_000,
1217 &PlatformAddress::P2pkh([0x01u8; 20]),
1218 &[key],
1219 );
1220 assert_eq!(data[data.len() - 33], 3);
1221 assert_eq!(&data[data.len() - 32..], &[0x44u8; 32]);
1222 }
1223 }
1224
1225 #[test]
1226 fn document_token_payment_layout_is_token_owner_contract_document_amount() {
1227 let data = document_token_payment_extra_sighash_data_v0(
1228 &[1u8; 32], &[2u8; 32], &[3u8; 32], &[4u8; 32], 10,
1229 );
1230 assert_eq!(data.len(), 136);
1231 assert_eq!(&data[..32], &[1u8; 32]);
1232 assert_eq!(&data[32..64], &[2u8; 32]);
1233 assert_eq!(&data[64..96], &[3u8; 32]);
1234 assert_eq!(&data[96..128], &[4u8; 32]);
1235 assert_eq!(&data[128..], &10u64.to_le_bytes());
1236 }
1237
1238 #[test]
1239 fn token_pool_paid_layouts_start_with_the_state_transition_type() {
1240 let transfer = token_shielded_transfer_with_shielded_fee_extra_sighash_data_v0(&[1u8; 32]);
1241 assert_eq!(transfer.len(), 33);
1242 assert_eq!(transfer[0], 26);
1243 assert_eq!(&transfer[1..], &[1u8; 32]);
1244
1245 let unshield =
1246 token_unshield_with_shielded_fee_extra_sighash_data_v0(&[1u8; 32], &[2u8; 32], 300);
1247 assert_eq!(unshield.len(), 73);
1248 assert_eq!(unshield[0], 27);
1249 assert_eq!(&unshield[1..33], &[1u8; 32]);
1250 assert_eq!(&unshield[33..65], &[2u8; 32]);
1251 assert_eq!(&unshield[65..], &300u64.to_le_bytes());
1252
1253 let purchase = token_purchase_from_shielded_pool_extra_sighash_data_v0(&[1u8; 32], 5, 900);
1254 assert_eq!(purchase.len(), 49);
1255 assert_eq!(purchase[0], 28);
1256 assert_eq!(&purchase[33..41], &5u64.to_le_bytes());
1257 assert_eq!(&purchase[41..], &900u64.to_le_bytes());
1258
1259 let fee = token_pool_fee_bundle_extra_sighash_data_v0(27, &[1u8; 32], &[3u8; 32]);
1260 assert_eq!(fee.len(), 65);
1261 assert_eq!(fee[0], 27);
1262 assert_eq!(&fee[1..33], &[1u8; 32]);
1263 assert_eq!(&fee[33..], &[3u8; 32]);
1264 }
1265
1266 #[test]
1267 fn token_burn_from_pool_layout_is_token_burner_amount() {
1268 let data = token_burn_from_pool_extra_sighash_data_v0(&[1u8; 32], &[2u8; 32], 300);
1269 assert_eq!(data.len(), 72);
1270 assert_eq!(&data[..32], &[1u8; 32]);
1271 assert_eq!(&data[32..64], &[2u8; 32]);
1272 assert_eq!(&data[64..], &300u64.to_le_bytes());
1273 }
1274
1275 #[test]
1276 fn outputs_only_token_pool_sighash_data_pins_its_v0_layout() {
1277 let token_id = [7u8; 32];
1280 let owner_id = [5u8; 32];
1281 let version = PlatformVersion::latest();
1282 for (action_type, tag) in [
1283 (TokenTransitionActionType::Shield, 0x80u8),
1284 (TokenTransitionActionType::MintToPool, 0x81),
1285 (TokenTransitionActionType::ClaimToPool, 0x82),
1286 (TokenTransitionActionType::DirectPurchaseToPool, 0x83),
1287 ] {
1288 let mut expected = Vec::with_capacity(65);
1289 expected.push(tag);
1290 expected.extend_from_slice(&token_id);
1291 expected.extend_from_slice(&owner_id);
1292 assert_eq!(
1293 token_pool_output_only_extra_sighash_data(
1294 action_type,
1295 &token_id,
1296 &owner_id,
1297 version
1298 )
1299 .expect("outputs-only kind"),
1300 expected,
1301 "v0 layout is frozen for {action_type}: tag (1) || token_id (32) || owner_id (32)"
1302 );
1303 }
1304 }
1305
1306 #[test]
1307 fn outputs_only_token_pool_sighash_data_separates_pools() {
1308 let version = PlatformVersion::latest();
1311 let a = token_pool_output_only_extra_sighash_data(
1312 TokenTransitionActionType::Shield,
1313 &[1u8; 32],
1314 &[5u8; 32],
1315 version,
1316 )
1317 .expect("shield tag");
1318 let b = token_pool_output_only_extra_sighash_data(
1319 TokenTransitionActionType::Shield,
1320 &[2u8; 32],
1321 &[5u8; 32],
1322 version,
1323 )
1324 .expect("shield tag");
1325 assert_ne!(
1326 a, b,
1327 "a bundle must not verify against another token's pool"
1328 );
1329 }
1330
1331 #[test]
1332 fn outputs_only_token_pool_sighash_data_separates_transition_kinds() {
1333 let version = PlatformVersion::latest();
1337 let token_id = [9u8; 32];
1338 let owner_id = [5u8; 32];
1339 let built: Vec<Vec<u8>> = [
1340 TokenTransitionActionType::Shield,
1341 TokenTransitionActionType::MintToPool,
1342 TokenTransitionActionType::ClaimToPool,
1343 TokenTransitionActionType::DirectPurchaseToPool,
1344 ]
1345 .into_iter()
1346 .map(|action_type| {
1347 token_pool_output_only_extra_sighash_data(action_type, &token_id, &owner_id, version)
1348 .expect("outputs-only kind")
1349 })
1350 .collect();
1351
1352 for (i, a) in built.iter().enumerate() {
1353 for b in built.iter().skip(i + 1) {
1354 assert_ne!(a, b, "each transition kind must get its own preimage");
1355 }
1356 }
1357 }
1358
1359 #[test]
1360 fn outputs_only_token_pool_sighash_data_refuses_every_other_kind() {
1361 let version = PlatformVersion::latest();
1365 for action_type in [
1369 TokenTransitionActionType::Burn,
1370 TokenTransitionActionType::Mint,
1371 TokenTransitionActionType::Transfer,
1372 TokenTransitionActionType::Freeze,
1373 TokenTransitionActionType::Unfreeze,
1374 TokenTransitionActionType::DestroyFrozenFunds,
1375 TokenTransitionActionType::Claim,
1376 TokenTransitionActionType::EmergencyAction,
1377 TokenTransitionActionType::ConfigUpdate,
1378 TokenTransitionActionType::DirectPurchase,
1379 TokenTransitionActionType::SetPriceForDirectPurchase,
1380 TokenTransitionActionType::BurnFromPool,
1381 TokenTransitionActionType::Unshield,
1382 TokenTransitionActionType::ShieldedTransfer,
1383 ] {
1384 let error = token_pool_output_only_extra_sighash_data(
1385 action_type,
1386 &[1u8; 32],
1387 &[5u8; 32],
1388 version,
1389 )
1390 .expect_err("only outputs-only pool bundles have a tag");
1391 assert!(
1392 matches!(error, ProtocolError::InvalidStateTransitionType(_)),
1393 "{action_type} must be refused, got {error:?}"
1394 );
1395 }
1396 }
1397
1398 #[test]
1399 fn outputs_only_token_pool_tags_cannot_collide_with_state_transition_types() {
1400 for tag in [
1407 TOKEN_SHIELD_BUNDLE_TAG,
1408 TOKEN_MINT_TO_POOL_BUNDLE_TAG,
1409 TOKEN_CLAIM_TO_POOL_BUNDLE_TAG,
1410 TOKEN_DIRECT_PURCHASE_TO_POOL_BUNDLE_TAG,
1411 ] {
1412 assert!(
1413 StateTransitionType::try_from(tag).is_err(),
1414 "state transition type {tag:#04x} now collides with an outputs-only bundle tag"
1415 );
1416 }
1417 }
1418
1419 mod credit_pool_outputs_only {
1420 use super::*;
1421 use crate::identity::state_transition::asset_lock_proof::chain::ChainAssetLockProof;
1422 use crate::identity::state_transition::asset_lock_proof::InstantAssetLockProof;
1423 use crate::util::hash::hash_double;
1424 use dashcore::transaction::special_transaction::asset_lock::AssetLockPayload;
1425 use dashcore::transaction::special_transaction::TransactionPayload;
1426 use dashcore::{InstantLock, OutPoint, ScriptBuf, Transaction, TxOut};
1427
1428 fn protocol_version(version: u32) -> &'static PlatformVersion {
1429 PlatformVersion::get(version).expect("known protocol version")
1430 }
1431
1432 fn chain_asset_lock_proof(outpoint: [u8; 36]) -> AssetLockProof {
1433 AssetLockProof::Chain(ChainAssetLockProof {
1434 core_chain_locked_height: 100,
1435 out_point: OutPoint::from(outpoint),
1436 })
1437 }
1438
1439 fn inputs(
1440 entries: &[(PlatformAddress, AddressNonce, Credits)],
1441 ) -> BTreeMap<PlatformAddress, (AddressNonce, Credits)> {
1442 entries
1443 .iter()
1444 .map(|(address, nonce, amount)| (*address, (*nonce, *amount)))
1445 .collect()
1446 }
1447
1448 #[test]
1449 fn should_pin_the_v0_layout_of_all_three_kinds() {
1450 let version = PlatformVersion::latest();
1454
1455 let shield_inputs = inputs(&[
1458 (PlatformAddress::P2sh([0x00; 20]), 1, 10),
1459 (PlatformAddress::P2pkh([0x11; 20]), 2, 20),
1460 ]);
1461 let mut funding = Vec::new();
1462 funding.push(0x00);
1463 funding.extend_from_slice(&[0x11; 20]);
1464 funding.push(0x01);
1465 funding.extend_from_slice(&[0x00; 20]);
1466 let mut expected = vec![0x84];
1467 expected.extend_from_slice(&Sha256::digest(&funding));
1468 assert_eq!(
1469 shield_extra_sighash_data(&shield_inputs, version).expect("shield"),
1470 expected,
1471 "Shield: 0x84 || SHA-256 of the input addresses in map order"
1472 );
1473
1474 let identity_id = [0x22u8; 32];
1475 let mut expected = vec![0x85];
1476 expected.extend_from_slice(&identity_id);
1477 assert_eq!(
1478 shield_from_identity_extra_sighash_data(&identity_id, version)
1479 .expect("shield from identity"),
1480 expected,
1481 "ShieldFromIdentity: 0x85 || identity id"
1482 );
1483
1484 let outpoint = [0x33u8; 36];
1485 let mut expected = vec![0x86];
1486 expected.extend_from_slice(&hash_double(outpoint));
1487 assert_eq!(
1488 shield_from_asset_lock_extra_sighash_data(
1489 &chain_asset_lock_proof(outpoint),
1490 version
1491 )
1492 .expect("shield from asset lock"),
1493 expected,
1494 "ShieldFromAssetLock: 0x86 || double SHA-256 of the locked outpoint"
1495 );
1496 }
1497
1498 #[test]
1499 fn should_bind_the_same_shield_funding_whatever_order_the_inputs_were_assembled_in() {
1500 let version = PlatformVersion::latest();
1504 let a = (PlatformAddress::P2pkh([0x01; 20]), 1, 100);
1505 let b = (PlatformAddress::P2sh([0x02; 20]), 2, 200);
1506 let c = (PlatformAddress::P2pkh([0x03; 20]), 3, 300);
1507
1508 let forward = shield_extra_sighash_data(&inputs(&[a, b, c]), version).expect("shield");
1509 let backward = shield_extra_sighash_data(&inputs(&[c, b, a]), version).expect("shield");
1510 let shuffled = shield_extra_sighash_data(&inputs(&[b, c, a]), version).expect("shield");
1511
1512 assert_eq!(forward, backward);
1513 assert_eq!(forward, shuffled);
1514 }
1515
1516 #[test]
1517 fn should_bind_who_funds_a_shield_but_not_its_nonces_or_amounts() {
1518 let version = PlatformVersion::latest();
1522 let first = PlatformAddress::P2pkh([0x01; 20]);
1523 let second = PlatformAddress::P2pkh([0x02; 20]);
1524 let base =
1525 shield_extra_sighash_data(&inputs(&[(first, 1, 100)]), version).expect("shield");
1526
1527 assert_eq!(
1528 base,
1529 shield_extra_sighash_data(&inputs(&[(first, 9, 100)]), version).expect("shield"),
1530 "the nonce must not be bound"
1531 );
1532 assert_eq!(
1533 base,
1534 shield_extra_sighash_data(&inputs(&[(first, 1, 999)]), version).expect("shield"),
1535 "the contributed amount must not be bound"
1536 );
1537 assert_ne!(
1538 base,
1539 shield_extra_sighash_data(&inputs(&[(second, 1, 100)]), version).expect("shield"),
1540 "another funding address is another owner"
1541 );
1542 assert_ne!(
1543 base,
1544 shield_extra_sighash_data(&inputs(&[(first, 1, 100), (second, 1, 100)]), version)
1545 .expect("shield"),
1546 "an added funding address is another owner"
1547 );
1548 assert_ne!(
1549 base,
1550 shield_extra_sighash_data(
1551 &inputs(&[(PlatformAddress::P2sh([0x01; 20]), 1, 100)]),
1552 version
1553 )
1554 .expect("shield"),
1555 "the address type is part of the owner, not only its hash"
1556 );
1557 }
1558
1559 #[test]
1560 fn should_keep_kinds_apart_when_their_owners_coincide() {
1561 let version = PlatformVersion::latest();
1565 let proof = chain_asset_lock_proof([0x44; 36]);
1566 let identity_id = proof.create_identifier().expect("identifier").to_buffer();
1567
1568 let from_lock =
1569 shield_from_asset_lock_extra_sighash_data(&proof, version).expect("asset lock");
1570 let from_identity =
1571 shield_from_identity_extra_sighash_data(&identity_id, version).expect("identity");
1572
1573 assert_eq!(&from_lock[1..], &from_identity[1..], "the owners coincide");
1574 assert_ne!(from_lock, from_identity, "the kinds must not");
1575 }
1576
1577 #[test]
1578 fn should_bind_the_whole_outpoint_of_the_asset_lock_not_only_its_transaction() {
1579 let version = PlatformVersion::latest();
1584 let credit_output = |value| TxOut {
1585 value,
1586 script_pubkey: ScriptBuf::new(),
1587 };
1588 let transaction = Transaction {
1589 version: 3,
1590 lock_time: 0,
1591 input: vec![],
1592 output: vec![],
1593 special_transaction_payload: Some(TransactionPayload::AssetLockPayloadType(
1594 AssetLockPayload {
1595 version: 0,
1596 credit_outputs: vec![credit_output(100_000), credit_output(200_000)],
1597 },
1598 )),
1599 };
1600 let txid = transaction.txid();
1601 let instant = |output_index| {
1602 AssetLockProof::Instant(InstantAssetLockProof::new(
1603 InstantLock::default(),
1604 transaction.clone(),
1605 output_index,
1606 ))
1607 };
1608 let bound = |proof: &AssetLockProof| {
1609 shield_from_asset_lock_extra_sighash_data(proof, version).expect("asset lock")
1610 };
1611
1612 assert_ne!(
1613 bound(&instant(0)),
1614 bound(&instant(1)),
1615 "another output of the same lock transaction is another owner"
1616 );
1617 assert_eq!(
1618 bound(&instant(1)),
1619 bound(&AssetLockProof::Chain(ChainAssetLockProof {
1620 core_chain_locked_height: 100,
1621 out_point: OutPoint::new(txid, 1),
1622 })),
1623 "the same outpoint is the same owner whichever proof presents it"
1624 );
1625 }
1626
1627 #[test]
1628 fn should_bind_nothing_at_protocol_versions_before_the_binding() {
1629 let lock = chain_asset_lock_proof([0x55; 36]);
1633 let funding = inputs(&[(PlatformAddress::P2pkh([0x01; 20]), 1, 100)]);
1634 for version in [12, 13] {
1635 let version = protocol_version(version);
1636 assert_eq!(version.dpp.methods.credit_pool_bundle_binding, None);
1637 assert!(shield_extra_sighash_data(&funding, version)
1638 .expect("shield")
1639 .is_empty());
1640 assert!(
1641 shield_from_identity_extra_sighash_data(&[0x66; 32], version)
1642 .expect("shield from identity")
1643 .is_empty()
1644 );
1645 assert!(shield_from_asset_lock_extra_sighash_data(&lock, version)
1646 .expect("shield from asset lock")
1647 .is_empty());
1648 }
1649 }
1650
1651 #[test]
1652 fn should_refuse_an_unknown_binding_version() {
1653 let mut version = PlatformVersion::latest().clone();
1656 version.dpp.methods.credit_pool_bundle_binding = Some(1);
1657 let lock = chain_asset_lock_proof([0x77; 36]);
1658
1659 for result in [
1660 shield_extra_sighash_data(&BTreeMap::new(), &version),
1661 shield_from_identity_extra_sighash_data(&[0x88; 32], &version),
1662 shield_from_asset_lock_extra_sighash_data(&lock, &version),
1663 ] {
1664 assert_matches::assert_matches!(
1665 result,
1666 Err(ProtocolError::UnknownVersionMismatch { received: 1, .. })
1667 );
1668 }
1669 }
1670
1671 #[test]
1672 fn credit_pool_outputs_only_tags_cannot_collide_with_state_transition_types() {
1673 for tag in [
1677 SHIELD_BUNDLE_TAG,
1678 SHIELD_FROM_IDENTITY_BUNDLE_TAG,
1679 SHIELD_FROM_ASSET_LOCK_BUNDLE_TAG,
1680 ] {
1681 assert!(
1682 StateTransitionType::try_from(tag).is_err(),
1683 "state transition type {tag:#04x} now collides with a credit pool bundle tag"
1684 );
1685 }
1686 }
1687
1688 #[test]
1689 fn outputs_only_bundle_tags_are_pairwise_distinct() {
1690 let tags = [
1693 TOKEN_SHIELD_BUNDLE_TAG,
1694 TOKEN_MINT_TO_POOL_BUNDLE_TAG,
1695 TOKEN_CLAIM_TO_POOL_BUNDLE_TAG,
1696 TOKEN_DIRECT_PURCHASE_TO_POOL_BUNDLE_TAG,
1697 SHIELD_BUNDLE_TAG,
1698 SHIELD_FROM_IDENTITY_BUNDLE_TAG,
1699 SHIELD_FROM_ASSET_LOCK_BUNDLE_TAG,
1700 ];
1701 for (i, a) in tags.iter().enumerate() {
1702 for b in tags.iter().skip(i + 1) {
1703 assert_ne!(a, b, "outputs-only bundle tag {a:#04x} is used twice");
1704 }
1705 }
1706 }
1707 }
1708}