Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
f01a88d
fix(storage): bound what one client can hold on a node
cryptskii Oct 1, 2026
92d0ace
fix(storage): immutable puts made at once never refuse each other
cryptskii Oct 1, 2026
1ef20cf
fix(sofi): a trader's single-root parent is what its lineage holds at…
cryptskii Oct 1, 2026
d2363ca
docs(conformance): pre-audit item 2, a single-root parent is what the…
cryptskii Oct 1, 2026
5afda74
fix(sdk): a member is reached over https and known by its certificate…
cryptskii Oct 1, 2026
cacecdf
fix(wallet): a send names its recipient by device id, never by alias
cryptskii Oct 1, 2026
ff953fc
docs(conformance): pre-audit item 6, a send names its recipient by de…
cryptskii Oct 1, 2026
2b56b99
Merge fix/storage-dos-and-transport-hardening into security/beta-pre-…
cryptskii Oct 1, 2026
d64df70
chore(deps): the client workspace's lock records the merged storage d…
cryptskii Oct 1, 2026
c6f95bc
feat(core): a root-cell occupant proves its own authority (A10, S20)
cryptskii Oct 1, 2026
d5b423d
docs(intent): MR-SOFI-0356 names held_at, the function that replaced …
cryptskii Oct 1, 2026
91e8ca6
feat(core): a fulfillment proves its device at K_ful(q) (A10, S20)
cryptskii Oct 1, 2026
6155608
docs(specs): DSM A10 and SoFi S20, the occupant proves its own authority
cryptskii Oct 1, 2026
f5cd5d9
docs(conformance): the item-1 matrix row names its test once
cryptskii Oct 1, 2026
96b870f
fix(sofi): junk can no longer exhaust a walk or hold a key or a posit…
cryptskii Oct 1, 2026
232f251
fix(core): decoders sized by their input; a nonce spent in its relati…
cryptskii Oct 1, 2026
b3bacd6
docs(conformance): §6.67, core decoders and the nonce scope (pre-audi…
cryptskii Oct 1, 2026
1e7b265
fix(core): a credit's source is held to account through its own segme…
cryptskii Oct 1, 2026
f53af76
test(core): a source walk past the budget is Incomplete and reads not…
cryptskii Oct 1, 2026
b9401fc
test(sdk): a walk an outage cut short records no frontier; recovery r…
cryptskii Oct 1, 2026
b45eb77
docs(conformance): pre-audit item 12, junk cannot exhaust a walk or h…
cryptskii Oct 1, 2026
f3e3419
Merge security/core-root-cell-binding (pre-audit item 1) into securit…
cryptskii Oct 1, 2026
2de9fa7
Merge security/core-decoders-and-nonce-scope at b3bacd66f (pre-audit …
cryptskii Oct 1, 2026
ecf6eb6
feat(core): a source's validated frontier is cached with the acceptan…
cryptskii Oct 1, 2026
ce16711
docs(spec): A8 holds a credit's source to account through its own seg…
cryptskii Oct 1, 2026
483b2c3
docs(spec): S20 decides and matches the position pair from its own by…
cryptskii Oct 1, 2026
5018e18
Merge security/core-decoders-and-nonce-scope at ce1671153 (pre-audit …
cryptskii Oct 1, 2026
70bcd47
Merge security/core-root-cell-binding at 483b2c369 (SoFi Amendment S2…
cryptskii Oct 1, 2026
51359a6
fix(sofi): a position pair is decided from its own bytes, the same wh…
cryptskii Oct 2, 2026
c1e4e6f
test(sdk): the nonce tests spend the nonce a transfer derives, not a …
cryptskii Oct 2, 2026
57d8dd6
docs(conformance): 12j closed; S20's pair rows Met; the nonce tests' …
cryptskii Oct 2, 2026
5a5f911
test(storage): a cell takes the largest route entry and refuses a byt…
cryptskii Oct 2, 2026
2d1b010
fix(sofi): the exercise carries the trader's signed C_q, so a withhel…
cryptskii Oct 2, 2026
a8dc6aa
Merge origin/main (#1094, #1095) into security/beta-pre-audit
cryptskii Oct 2, 2026
820d88b
Merge test/storage-cell-value-cap at 5a5f911ee (the cell value cap's …
cryptskii Oct 2, 2026
680eb8c
test(sdk): the member that answers first with other bytes is reached …
cryptskii Oct 2, 2026
7f5ad52
fix(sofi): the next operation at a vault registers a withheld pair fr…
cryptskii Oct 2, 2026
a92ddfe
Merge remote-tracking branch 'origin/test/tls-substitute-member' into…
cryptskii Oct 2, 2026
f17b6be
docs(conformance): 12e closed, with the owner's ruling on who relays …
cryptskii Oct 2, 2026
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
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 19 additions & 6 deletions ci/sofi_relay_is_party_neutral.sh
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,30 @@ for entry in "${FORBIDDEN[@]}"; do
done
echo " ✓ the relay holds no device state"

# 2. The relay's entry points take a set and a content address, nothing more.
for fn in relay_fulfillment relay_position_pair; do
# 2. The relay's entry points take a set and public coordinates, nothing more:
# a fulfillment's content address, or the vault key an exercise holds
# (SoFi Amendment S20: the exercise carries everything its pair needs).
relay_entry() {
local fn="$1" what="$2"; shift 2
local sig
sig=$(awk "/pub async fn ${fn}\\(/{f=1} f{print} f&&/-> Result/{exit}" "$relay")
[[ -n "$sig" ]] || { echo "[FAIL] $relay no longer defines $fn"; exit 1; }
if ! grep -q 'set: &StorageSet' <<<"$sig" || ! grep -q 'fulfillment_id: &D32' <<<"$sig"; then
echo "[FAIL] $fn no longer takes exactly (committed set, fulfillment id):"
for param in 'set: &StorageSet' "$@"; do
if ! grep -qF "$param" <<<"$sig"; then
echo "[FAIL] $fn no longer takes exactly ($what):"
echo "$sig"
exit 1
fi
done
if [[ $(grep -c ':' <<<"$(sed -n '/(/,/)/p' <<<"$sig" | grep -vE 'pub async fn')") -ne $(( $# + 1 )) ]]; then
echo "[FAIL] $fn takes more than ($what):"
echo "$sig"
exit 1
fi
done
echo " ✓ a relay is reached with a committed set and a content address"
}
relay_entry relay_fulfillment "committed set, fulfillment id" 'fulfillment_id: &D32'
relay_entry relay_exercise "committed set, vault key" 'vault_id: &D32' 'parent_root: &D32' 'attempt: u64'
echo " ✓ a relay is reached with a committed set and public coordinates"

# 3. The owner-local half still requires the owning device. A relay-shaped
# signature here would be the same conflation wearing the other face.
Expand Down
3 changes: 3 additions & 0 deletions dsm_client/deterministic_state_machine/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions dsm_client/deterministic_state_machine/dsm/src/ccb/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,10 @@ pub mod class {
/// `𝒞_E^pre` and accepted only because it hashes to the leaf the core
/// states.
pub const SOFI_TRADER_PRE_BALANCE: u16 = 0x0061;
/// `0x0062` — `C_q` as it occupies `K_root(q)`: the derived claim and the
/// trader's signature over it, under a key the claim's own `AttA` binds
/// to its `DevID` (DSM Amendment A10, SoFi Amendment S20).
pub const SOFI_SIGNED_RESOLUTION_CLAIM: u16 = 0x0062;
}

/// Discriminants **allocated but not encodable** — see [`class`] for the ones
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,10 @@ impl DevTreeProof {
path_bits.push(bit);
}

if offset + num_siblings * 32 != data.len() {
// In u64: on a 32-bit target `num_siblings * 32` wraps for a hostile
// count, and a wrapped length would pass this check while the copies
// below index past the input.
if offset as u64 + num_siblings as u64 * 32 != data.len() as u64 {
return None;
}

Expand Down Expand Up @@ -312,6 +315,21 @@ impl DeviceTree {
mod tests {
use super::*;

/// A proof that names more siblings than it carries is refused. The count
/// here is `2^27`, whose byte length `2^32` wraps to 0 on a 32-bit target,
/// so a length check in `usize` would accept these nine bytes there.
#[test]
fn a_proof_naming_more_siblings_than_it_carries_is_refused() {
assert!(
DevTreeProof::from_bytes(&[0x00, 0x00, 0x00, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00])
.is_none()
);
assert!(
DevTreeProof::from_bytes(&[0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00])
.is_none()
);
}

#[test]
fn test_single_device_tree() {
let devid = [42u8; 32];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ pub(super) const TAGS: &[TaggedHashDomain<'static>] = &[
TAG_DSM_ML_KEM_SEED,
TAG_DSM_NEXT_ENTROPY,
TAG_DSM_NONCE,
TAG_DSM_RELATIONSHIP_NONCE,
TAG_DSM_PK_HASH,
TAG_DSM_PRECOMMIT,
TAG_DSM_PRECOMMIT_INVALIDATION_PROOF_V2,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,10 @@ pub const TAG_DSM_DETERMINISTIC_NONCE_32: TaggedHashDomain<'static> =
pub const TAG_DSM_DETERMINISTIC_NONCE_GCM: TaggedHashDomain<'static> =
crate::tagged_domain!(b"DSM/deterministic-nonce-gcm");
pub const TAG_DSM_NONCE: TaggedHashDomain<'static> = TaggedHashDomain::from_static(b"DSM/nonce");
/// A transfer nonce as it is spent: `H(tag ‖ relationship_key ‖ nonce)`. A
/// nonce is unique within the relationship that carries it, so a transfer of
/// another relationship cannot spend it.
pub const TAG_DSM_RELATIONSHIP_NONCE: TaggedHashDomain<'static> =
crate::tagged_domain!(b"DSM/relationship-nonce/v1");
pub const TAG_DSM_WALK_SEED: TaggedHashDomain<'static> = crate::tagged_domain!(b"DSM/walk-seed");
pub const TAG_DSM_WALK_STEP: TaggedHashDomain<'static> = crate::tagged_domain!(b"DSM/walk-step");
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ pub const TAG_DSM_SOFI_FULFILLMENT_ID: TaggedHashDomain<'static> =
/// `m_F = H(tag ‖ CCB(TraderFulfillmentBody))`.
pub const TAG_DSM_SOFI_FULFILLMENT_SIGN: TaggedHashDomain<'static> =
crate::tagged_domain!(b"DSM/sofi/fulfillment-sign/v1");
/// `m_C = H(tag ‖ CCB(C_q) ‖ u16be(alg) ‖ u32be(|key|) ‖ key ‖ AttA)` — what
/// the trader signs to make `C_q` an occupant of `K_root(q)` (SoFi Amendment
/// S20).
pub const TAG_DSM_SOFI_RESOLUTION_CLAIM_SIGN: TaggedHashDomain<'static> =
crate::tagged_domain!(b"DSM/sofi/resolution-claim-sign/v1");

// ── Successor attempts and route outcome (F6, F7) ──────────────────────────

Expand Down Expand Up @@ -196,6 +201,7 @@ pub(crate) const SOFI_TAGS: &[TaggedHashDomain<'static>] = &[
TAG_DSM_SOFI_FULFILLMENT,
TAG_DSM_SOFI_FULFILLMENT_ID,
TAG_DSM_SOFI_FULFILLMENT_SIGN,
TAG_DSM_SOFI_RESOLUTION_CLAIM_SIGN,
TAG_DSM_SOFI_SUCC_CELL_V2,
TAG_DSM_SOFI_SUCC_ATTEMPT,
TAG_DSM_SOFI_STORAGE_SEED_V4,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,9 @@ mod tests {
// network hash domain's only input.
// +1 with the TraderPreBalance object namespace (SoFi Amendment S12).
// +1 with the vault token locator (SoFi Amendment S16).
const EXPECTED_TAG_COUNT: usize = 352;
// +1 with the signed resolution claim's digest (SoFi Amendment S20), and
// +1 with the relationship-scoped transfer nonce (pre-audit item 5).
const EXPECTED_TAG_COUNT: usize = 354;

/// Scan the crate source for every declared domain-tag constant.
///
Expand Down
16 changes: 13 additions & 3 deletions dsm_client/deterministic_state_machine/dsm/src/economic/claim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,13 +154,19 @@ impl EconomicAdmissionManifest {
}
}

/// `0x001B` schema 1 — the signed body a trader registers at its economic
/// `0x001B` schema 2 — the signed body a trader registers at its economic
/// position.
///
/// `root_register_storage_set_id` is a member of the **signed** body, not
/// transport context. A claim that did not commit the set it was written to
/// could be lifted from one network's register into another's, which is
/// precisely the substitution the network scoping exists to prevent.
///
/// `claimant_att_a` is the trader device's `AttA` (DSM Amendment A10): the
/// claim proves its own authority for the cell, because `DevID = H(AK ‖ AttA)`
/// and recognition requires `derive_devid(claimant_public_key, claimant_att_a)
/// == trader_devid`. A key that does not derive the named device signs no
/// claim that can occupy that device's cell.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EconomicRootClaimBody {
pub trader_genesis: [u8; 32],
Expand All @@ -171,11 +177,12 @@ pub struct EconomicRootClaimBody {
pub root_register_storage_set_id: [u8; 32],
pub signature_alg: u16,
pub claimant_public_key: Vec<u8>,
pub claimant_att_a: [u8; 32],
}

impl CcbObject for EconomicRootClaimBody {
const CLASS: u16 = class::ECONOMIC_ROOT_CLAIM_BODY;
const SCHEMA: u16 = 1;
const SCHEMA: u16 = 2;
}

impl EconomicRootClaimBody {
Expand All @@ -191,6 +198,7 @@ impl EconomicRootClaimBody {
root_register_storage_set_id: [u8; 32],
signature_alg: u16,
claimant_public_key: &[u8],
claimant_att_a: [u8; 32],
) -> Result<Self, CcbError> {
let expected = sigalg::public_key_len(signature_alg)
.ok_or(CcbError::UnknownSignatureAlg { alg: signature_alg })?;
Expand All @@ -210,10 +218,11 @@ impl EconomicRootClaimBody {
root_register_storage_set_id,
signature_alg,
claimant_public_key: claimant_public_key.to_vec(),
claimant_att_a,
})
}

/// Fields 1..8 in registry order.
/// Fields 1..9 in registry order.
pub fn encode(&self) -> Result<Vec<u8>, CcbError> {
let mut out = Vec::new();
push_envelope::<Self>(&mut out);
Expand All @@ -225,6 +234,7 @@ impl EconomicRootClaimBody {
push_digest32(&mut out, &self.root_register_storage_set_id); // 6
push_u16(&mut out, self.signature_alg); // 7
push_bytes(&mut out, &self.claimant_public_key)?; // 8
push_digest32(&mut out, &self.claimant_att_a); // 9
Ok(out)
}

Expand Down
Loading
Loading