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
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,7 @@ pub fn advance_validated(
genesis,
device_id,
witness,
registered.economic_position(),
)
.map_err(EconomicValidationError::WriteSet)?;
}
Expand Down
403 changes: 398 additions & 5 deletions dsm_client/deterministic_state_machine/dsm/src/economic/write_set.rs

Large diffs are not rendered by default.

42 changes: 42 additions & 0 deletions dsm_client/deterministic_state_machine/dsm/src/sofi/signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@ mod tests {
let operation = Operation::SofiVaultCreate {
genesis_preimage: vec![0x01, 0x02],
creation: vec![0x03, 0x04],
market_policy_preimage: vec![0x00, 0x07],
funding_a_policy_commit: [0x5C; 32],
funding_b_policy_commit: [0x5D; 32],
signature: Vec::new(),
Expand All @@ -403,13 +404,54 @@ mod tests {
let signed = Operation::SofiVaultCreate {
genesis_preimage: vec![0x01, 0x02],
creation: vec![0x03, 0x04],
market_policy_preimage: vec![0x00, 0x07],
funding_a_policy_commit: [0x5C; 32],
funding_b_policy_commit: [0x5D; 32],
signature: sphincs_sign(&sk, &bytes).unwrap(),
};
assert_eq!(verify_operation(&signed, &pk), Ok(()));
}

/// THE CARRIED MARKET POLICY IS UNDER THE SIGNATURE, and asserting it is
/// not tautological.
///
/// Coverage is structural — `operation_signing_bytes` is the whole
/// canonical encoding with the signature cleared — but only while the
/// field is IN that encoding. Deleting its line from `Operation::to_bytes`
/// would silently take it back out from under the signature, leaving the
/// policy object substitutable after signing while every other check still
/// passed. This test is what goes red if that happens.
#[test]
fn altering_the_carried_market_policy_after_signing_is_refused() {
let (pk, sk) = keys();
let build = |policy: Vec<u8>, signature: Vec<u8>| Operation::SofiVaultCreate {
genesis_preimage: vec![0x01, 0x02],
creation: vec![0x03, 0x04],
market_policy_preimage: policy,
funding_a_policy_commit: [0x5C; 32],
funding_b_policy_commit: [0x5D; 32],
signature,
};
let honest = vec![0x00, 0x07, 0x00, 0x01];
let bytes = crate::core::state_machine::transition::operation_signing_bytes(&build(
honest.clone(),
Vec::new(),
));
let signature = sphincs_sign(&sk, &bytes).unwrap();
assert_eq!(
verify_operation(&build(honest.clone(), signature.clone()), &pk),
Ok(())
);

// ONLY the policy bytes differ, and the signature is the same one.
let swapped = vec![0x00, 0x07, 0x00, 0x02];
assert_ne!(swapped, honest);
assert!(
verify_operation(&build(swapped, signature), &pk).is_err(),
"the carried market policy must be covered by the operation signature"
);
}

/// The body's own key cannot introduce itself: a signature that verifies
/// under the key the body names is still refused when that is not the
/// signer the caller proved.
Expand Down
37 changes: 30 additions & 7 deletions dsm_client/deterministic_state_machine/dsm/src/types/operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -668,18 +668,36 @@ pub enum Operation {
genesis_preimage: Vec<u8>,
/// Canonical `VaultCreation` bytes (class `0x005B`).
creation: Vec<u8>,
/// Canonical `MarketPolicy` bytes (class `0x0007`) — the EXACT policy
/// object the genesis state names by content address.
///
/// CARRIED, so that acceptance is a function of the operation's bytes
/// and the authenticated pre-state alone. `semantic_write_set` is
/// pure: it cannot resolve `VaultStateLeaf.market_policy` to a pair,
/// and making it fetch would put a resolver, storage availability and
/// foreign-walk liveness between a local acceptance decision and its
/// answer. 72 fixed-width bytes against the ~50KB signature this
/// operation already carries.
///
/// NOT a second source of market truth: Core re-addresses these bytes
/// under the market-policy namespace and refuses unless the address is
/// the one `state.market_policy` commits. Bytes that do not
/// authenticate to what the state named establish nothing.
market_policy_preimage: Vec<u8>,
/// The two assets the funding is debited from, in canonical order
/// (`a < b`).
///
/// SIGNED EXECUTION COORDINATES, not a second source of market truth.
/// `semantic_write_set` is pure — it cannot resolve
/// `VaultStateLeaf.market_policy` to a pair — so without these the
/// debit P15-12 requires cannot be derived from the operation at all,
/// and balances would move outside any declared write set.
/// Without them the debit P15-12 requires could not be derived from
/// the operation at all, and balances would move outside any declared
/// write set.
///
/// The authority remains the market policy the vault state commits:
/// `genesis_accepted` resolves it by content address and refuses
/// unless these two equal the pair it decodes.
/// The authority is the market policy the vault state commits:
/// `semantic_write_set` decodes `market_policy_preimage` — after
/// holding it to that address — and refuses unless these two equal
/// the pair it reads out. Until that binding landed the check lived
/// only in the SDK producer, so a different producer could name any
/// two assets and Core refused nothing.
funding_a_policy_commit: [u8; 32],
funding_b_policy_commit: [u8; 32],
/// SPHINCS+ over the operation's canonical unsigned bytes. A creation
Expand Down Expand Up @@ -1015,13 +1033,15 @@ impl Operation {
SofiVaultCreate {
genesis_preimage,
creation,
market_policy_preimage,
funding_a_policy_commit,
funding_b_policy_commit,
signature,
} => {
put_u8(&mut out, 35);
put_bytes(&mut out, genesis_preimage);
put_bytes(&mut out, creation);
put_bytes(&mut out, market_policy_preimage);
put_bytes(&mut out, funding_a_policy_commit);
put_bytes(&mut out, funding_b_policy_commit);
put_bytes(&mut out, signature);
Expand Down Expand Up @@ -2213,6 +2233,9 @@ impl Operation {
35 => SofiVaultCreate {
genesis_preimage: get_bytes(&mut input)?,
creation: get_bytes(&mut input)?,
// In the encoder's order. The policy object the genesis state
// names, carried so acceptance needs no resolver.
market_policy_preimage: get_bytes(&mut input)?,
// Both funding commits, in the encoder's order. Dropping
// either would decode to an operation that debits different
// assets than the one whose signature was checked.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -802,7 +802,7 @@ fn a_setup_transition_binds_its_position_and_its_derived_root() {
&G,
&DEV,
&dsm::economic::admission::dsm_economic_operation_id(&G, &DEV, &C_DSM_PLUS),
&dsm::economic::write_set::EconomicPreState::new(&std::collections::BTreeMap::new()),
&dsm::economic::write_set::EconomicPreState::new(&std::collections::BTreeMap::new(), 0),
&mut build_tree,
&dsm::economic::write_set::CreditSourceFacts::None,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ fn fixture(
&G,
&DEV,
&[0x42u8; 32],
&EconomicPreState::new(&balances),
&EconomicPreState::new(&balances, 0),
&mut tree,
&CreditSourceFacts::AuthorizedIssuance {
issuance_authorization_addr: evidence_addr,
Expand Down Expand Up @@ -313,7 +313,7 @@ fn fixture_with_stranger(signer_count: usize, amount: u64) -> Fixture {
&G,
&DEV,
&[0x42u8; 32],
&EconomicPreState::new(&balances),
&EconomicPreState::new(&balances, 0),
&mut tree,
&CreditSourceFacts::AuthorizedIssuance {
issuance_authorization_addr: evidence_addr,
Expand Down
Loading
Loading