Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
3cf342f
feat(platform-wallet): update an identity at a revision the caller names
jeanpierreroma Oct 18, 2026
fde97b1
fix(platform-wallet): bump the cached revision to the one the update …
jeanpierreroma Oct 18, 2026
7ecb917
test(platform-wallet): cover the revision refusal and the landed-upda…
jeanpierreroma Oct 19, 2026
8150967
Merge remote-tracking branch 'origin/v5.1-dev' into feat/platform-wal…
jeanpierreroma Oct 19, 2026
d020575
fix(platform-wallet): keep a newer cached identity from older updates…
jeanpierreroma Oct 19, 2026
7331388
feat(platform-wallet-ffi): report a revision mismatch with its own re…
jeanpierreroma Oct 19, 2026
d58a809
fix(platform-wallet): store a newer cache's keys when an older update…
jeanpierreroma Oct 19, 2026
aa2b6a7
fix(platform-wallet): keep stored breadcrumbs and cover the third fet…
jeanpierreroma Oct 19, 2026
8182dfc
fix(platform-wallet): give stored disabled keys the wallet slot's bre…
jeanpierreroma Oct 19, 2026
bb4380b
fix(platform-wallet): keep keys an older answer has and the cache lacks
jeanpierreroma Oct 19, 2026
f8d3f77
fix(platform-wallet): return the keys a dropped load took into the cache
jeanpierreroma Oct 19, 2026
24c62da
style(platform-wallet): import at the top in the new code and fixtures
jeanpierreroma Oct 19, 2026
b0d364f
fix(platform-wallet): fold older fetches per field and store the land…
jeanpierreroma Oct 19, 2026
dab42a4
fix(platform-wallet): store a landed update in one changeset and keep…
jeanpierreroma Oct 19, 2026
bea3aa2
fix(platform-wallet): store the snapshot when a landed update meets a…
jeanpierreroma Oct 19, 2026
e5cbeba
fix(platform-wallet): prefer Platform's disable time and match the pa…
jeanpierreroma Oct 19, 2026
37c3326
refactor(platform-wallet): one disable rule for both landed paths and…
jeanpierreroma Oct 19, 2026
3508dce
fix(platform-wallet): an older fetch no longer brings its balance or …
jeanpierreroma Oct 19, 2026
840209c
fix(platform-wallet): fold discovered identities, order fetched balan…
jeanpierreroma Oct 19, 2026
88510b8
fix(platform-wallet): keep the higher key limit, retry a fetched disa…
jeanpierreroma Oct 19, 2026
e0b1d7c
fix(platform-wallet): queue refused key rows for the next store, take…
jeanpierreroma Oct 20, 2026
144958e
fix(platform-wallet): refresh stores every key it changes, SQLite kee…
jeanpierreroma Oct 20, 2026
4d1f50a
feat(platform-wallet)!: let an identity update declare the keys it ad…
jeanpierreroma Oct 20, 2026
2c31b5f
fix(platform-wallet): disables carry no breadcrumb, a buffered merge …
jeanpierreroma Oct 20, 2026
8b82cfb
fix(platform-wallet): only a path that knows a key's origin creates i…
jeanpierreroma Oct 20, 2026
f589e11
fix(platform-wallet): a key a refresh adds stays rowless until its or…
jeanpierreroma Oct 20, 2026
bbf2673
fix(platform-wallet): a breadcrumb-less key row means unknown origin,…
jeanpierreroma Oct 20, 2026
ec3e225
test(platform-wallet): a load needs one write to store a new identity
jeanpierreroma Oct 20, 2026
2f3561d
fix(platform-wallet): add_keys and disable_keys queue refused rows, c…
jeanpierreroma Oct 20, 2026
d6d43e3
fix(platform-wallet)!: tell an unknown key origin from a rejected one…
jeanpierreroma Oct 20, 2026
9677750
fix(platform-wallet): an unknown-origin key row keeps a rejection it …
jeanpierreroma Oct 20, 2026
b016e9e
fix(platform-wallet): a rejection refutes only the coordinates it che…
jeanpierreroma Oct 20, 2026
bb184f5
fix(platform-wallet): a rejection drops only its slot's pending repai…
jeanpierreroma Oct 21, 2026
67fbf95
fix(platform-wallet): a rejection clears only the persisting wallet's…
jeanpierreroma Oct 21, 2026
18bc03f
fix(platform-wallet)!: combine a key's rejections as a set, scope Kot…
jeanpierreroma Oct 21, 2026
a683ade
fix(platform-wallet): rebuild a pending Kotlin repair under its bread…
jeanpierreroma Oct 21, 2026
67ad5ba
fix(platform-wallet): leave legacy breadcrumb owners unknown, keep a …
jeanpierreroma Oct 21, 2026
073b8cd
fix(platform-wallet): trace a constraint failure to the row it refuses
jeanpierreroma Oct 21, 2026
1f52ec1
fix(swift-sdk): keep the signer alive through the identity update FFI…
jeanpierreroma Oct 21, 2026
512bbcb
fix(platform-wallet): trace a constraint failure to the snapshot befo…
jeanpierreroma Oct 21, 2026
fcf69a6
fix(platform-wallet-ffi): refuse a null key array with a non-zero count
jeanpierreroma Oct 21, 2026
fd85f8d
test(platform-wallet-storage): pin each check on a stored identity ke…
jeanpierreroma Oct 21, 2026
6131d4e
fix(swift-sdk): leave a breadcrumb with no recorded owner to its unkn…
jeanpierreroma Oct 21, 2026
f52440d
fix(kotlin-sdk): cancel a key repair only on its own owner's rejectio…
jeanpierreroma Oct 21, 2026
03331ba
refactor(kotlin-sdk): apply Rust's key-origin decisions mechanically …
jeanpierreroma Oct 22, 2026
fdf8bf5
refactor(platform-wallet): fold fetched identities without the key-ro…
jeanpierreroma Oct 22, 2026
ad09c6a
refactor(platform-wallet): return the identity FFI to base apart from…
jeanpierreroma Oct 22, 2026
5b3840f
refactor(platform-wallet-storage): keep a stored breadcrumb on the sa…
jeanpierreroma Oct 22, 2026
60b0dfa
refactor(kotlin-sdk): drop the key-origin repair and Room schema 16, …
jeanpierreroma Oct 22, 2026
54135e6
refactor(swift-sdk): drop externalKeyIds and the persister's key-orig…
jeanpierreroma Oct 22, 2026
d49fa67
test(platform-wallet): pin that a load by index stores a new identity…
jeanpierreroma Oct 22, 2026
d62a921
docs(platform-wallet): state that a key entry without coordinates kee…
jeanpierreroma Oct 22, 2026
650fd45
fix(platform-wallet): store a landed update's disables when the cache…
jeanpierreroma Oct 22, 2026
8e5af24
fix(platform-wallet): take back a revision a base build put past the …
jeanpierreroma Oct 22, 2026
2028be0
fix(platform-wallet): lend a buffered breadcrumb only to the same key…
jeanpierreroma Oct 22, 2026
0198486
docs(platform-wallet): mention the equal-keys exception in the update…
jeanpierreroma Oct 22, 2026
0fbfba3
revert(platform-wallet): drop the equal-keys revision recovery
jeanpierreroma Oct 23, 2026
6a9f909
fix(platform-wallet): take a fetch's key states at its revision and k…
jeanpierreroma Oct 23, 2026
693fd17
fix(platform-wallet-storage): lend a held breadcrumb only from a vali…
jeanpierreroma Oct 23, 2026
2b8ba90
fix(platform-wallet): take Drive's revision when it refuses an update…
jeanpierreroma Oct 23, 2026
b891221
Merge remote-tracking branch 'origin/v5.1-dev' into feat/platform-wal…
jeanpierreroma Oct 23, 2026
2be1862
fix(platform-wallet): take a refused update's revision only from a pr…
jeanpierreroma Oct 23, 2026
f8b1bb3
fix(platform-wallet): take a refused update's revision only from a pr…
jeanpierreroma Oct 23, 2026
2aa05cd
fix(platform-wallet): re-send an update Drive refused below the cache…
jeanpierreroma Oct 23, 2026
d67e5a5
fix(platform-wallet): re-send a refused update only once proofs show …
jeanpierreroma Oct 23, 2026
a6e23a6
fix(platform-wallet): fail a refusal proved at u64::MAX instead of ov…
jeanpierreroma Oct 24, 2026
d415e70
fix(kotlin-sdk): pass the update revision through addKey and disableKeys
jeanpierreroma Oct 24, 2026
36c02c8
fix(platform-wallet): order key states after a restore by the restore…
jeanpierreroma Oct 24, 2026
f4d318f
fix(platform-wallet): keep the key watermark monotonic
jeanpierreroma Oct 24, 2026
58d0fc9
docs(kotlin-sdk): note that Kotlin has no next-revision reader for an…
jeanpierreroma Oct 25, 2026
cd00c9a
Merge remote-tracking branch 'origin/v5.1-dev' into feat/platform-wal…
jeanpierreroma Oct 26, 2026
9080abf
fix(platform-wallet)!: go back to lowering the cache on Drive's revis…
jeanpierreroma Oct 26, 2026
610aa57
fix(platform-wallet): store a landed revision with its keys as one write
jeanpierreroma Oct 26, 2026
bf9b415
Merge remote-tracking branch 'origin/v5.1-dev' into feat/platform-wal…
jeanpierreroma Oct 26, 2026
51d7be8
refactor(platform-wallet): drop the revision pin from the identity up…
jeanpierreroma Oct 28, 2026
8df7515
fix(platform-wallet): tell the caller not to resubmit a landed update…
jeanpierreroma Oct 28, 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
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
//! the key with bincode's native `Encode`/`Decode` and rides the surrounding
//! fields on the serde encoder, keeping one blob per row.

use rusqlite::{params, Connection, Transaction};
use rusqlite::{params, Connection, OptionalExtension, Transaction};
use serde::{Deserialize, Serialize};

use dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0;
Expand Down Expand Up @@ -126,6 +126,16 @@ fn map_fk_violation(
/// cross-checked against it so the typed columns and the blob stay aligned.
/// A cross-wallet write is refused with
/// [`WalletStorageError::IdentityKeyWalletMismatch`].
///
/// An upsert without a derivation breadcrumb keeps the one the same row
/// already holds, as the Swift and Kotlin persisters do (they keep it on any
/// upsert of the row; this writer also checks the row first): a write that only
/// knows the key's new state (a refresh, a raised limit, a disable) must not
/// turn a wallet-derived key watch-only. Only a row that holds the same
/// public key (type, data and hash) and passes the reader's checks
/// (`contradicts_columns`) lends its breadcrumb; any other stored row (one
/// that does not decode, is over the blob size limit, contradicts its
/// columns or holds another key) holds nothing to keep and is replaced.
pub fn apply(
tx: &Transaction<'_>,
wallet_id: &WalletId,
Expand Down Expand Up @@ -161,6 +171,12 @@ pub fn apply(
// 32 zero bytes instead would produce a row that matches no
// wallet, no NULL-scoped reader, and no guard.
let wallet_id_param = super::wallet_id_to_param(wallet_id);
let mut stored = tx.prepare_cached(
"SELECT public_key_blob, public_key_hash FROM identity_keys \
WHERE identity_id = ?1 AND key_id = ?2 AND wallet_id IS ?3 \
AND length(public_key_blob) <= ?4",
)?;
let size_limit = i64::try_from(blob::BLOB_SIZE_LIMIT_BYTES).unwrap_or(i64::MAX);
for ((identity_id, key_id), entry) in &cs.upserts {
// Typed columns and blob fields must agree so a row can never
// diverge on disk.
Expand All @@ -172,7 +188,40 @@ pub fn apply(
return Err(WalletStorageError::IdentityKeyEntryMismatch);
}
}
let wire = IdentityKeyWire::from_entry(entry)?;
let mut wire = IdentityKeyWire::from_entry(entry)?;
if wire.derivation_indices.is_none() {
let held: Option<(Vec<u8>, Vec<u8>)> = stored
.query_row(
params![
identity_id.as_slice(),
i64::from(*key_id),
wallet_id_param,
size_limit
],
|row| Ok((row.get(0)?, row.get(1)?)),
)
.optional()?;
let held = held.and_then(|(payload, typed_public_key_hash)| {
decode_entry(&payload).ok().filter(|held| {
!contradicts_columns(
held,
identity_id,
*key_id,
&typed_public_key_hash,
wallet_id,
)
})
});
if let Some(held) = held {
let same_key = held.public_key.key_type() == entry.public_key.key_type()
&& held.public_key.data() == entry.public_key.data()
&& held.public_key_hash == entry.public_key_hash;
if same_key && held.derivation_indices.is_some() {
wire.wallet_id = held.wallet_id;
wire.derivation_indices = held.derivation_indices;
}
}
}
let entry_blob = blob::encode(&wire)?;
stmt.execute(params![
wallet_id_param,
Expand Down Expand Up @@ -217,6 +266,25 @@ pub fn apply(
Ok(())
}

/// Whether a decoded row names a different identity, key, public-key hash
/// or wallet than the typed columns it was selected by: corruption, which
/// the reader skips and the writer never borrows a breadcrumb from.
fn contradicts_columns(
entry: &IdentityKeyEntry,
identity_id: &Identifier,
key_id: KeyID,
typed_public_key_hash: &[u8],
wallet_id: &WalletId,
) -> bool {
entry.identity_id != *identity_id
|| entry.key_id != key_id
|| entry.public_key.id() != key_id
|| entry.public_key_hash[..] != typed_public_key_hash[..]
|| entry
.wallet_id
.is_some_and(|entry_wallet_id| entry_wallet_id != *wallet_id)
}

/// Decode an `identity_keys.public_key_blob` cell back to the entry.
pub fn decode_entry(payload: &[u8]) -> Result<IdentityKeyEntry, WalletStorageError> {
let wire: IdentityKeyWire = blob::decode(payload)?;
Expand Down Expand Up @@ -285,14 +353,13 @@ pub fn load_state(
// `public_key_hash` is the indexed lookup column while the blob is
// what callers receive, so a divergence makes a key findable under a
// hash it does not carry.
let contradicts_columns = entry.identity_id != identity_id
|| entry.key_id != key_id
|| entry.public_key.id() != key_id
|| entry.public_key_hash[..] != typed_public_key_hash[..]
|| entry
.wallet_id
.is_some_and(|entry_wallet_id| entry_wallet_id != *wallet_id);
if contradicts_columns {
if contradicts_columns(
&entry,
&identity_id,
key_id,
&typed_public_key_hash,
wallet_id,
) {
ctx.tolerate(
LoadSite::IdentityKeyRow,
WalletStorageError::IdentityKeyEntryMismatch,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ mod sqlite_foreign_keys;
mod sqlite_identity_hard_delete;
mod sqlite_identity_index_concurrency;
mod sqlite_identity_index_uniqueness;
mod sqlite_identity_key_restore;
mod sqlite_identity_keys_reader;
mod sqlite_identity_scan_state;
mod sqlite_legacy_state_migration;
Expand Down
Loading
Loading