← Node dashboard

Password Rotation Protocol

KEL-backed per-site password ratchet. Node never stores passwords.

High-level overview

A user holds one identity (seed + master password → key K0) and incepts it on-chain as a Key Event Log (KEL). Each app or website gets its own off-chain branch derived from K0 plus a site id. Using a password always rotates that branch: the authenticator proves the current secret and commits a hash of the next one.

Closest analogues: KERI for the key log, S/KEY / Lamport OTP for “store hash of next secret,” BIP32 for per-site derivation from K0.

Roles

RoleImplementationHolds
Identity / authenticatorExtension, Android Yada PasswordSeed, master password, per-site keys and plaintext passwords
Relying partyThis node’s harness, Yada Auth DemoNext password PHC only (plus optional nonce)
LedgerYadaCoin nodeOn-chain identity KEL + off-chain site branch KELs

Deep dive

1. Identity inception

K0 = BIP32-style derive(seed, master password). One on-chain inception transaction is signed by K0 with prerotated_key_hash = K1 and twice_prerotated_key_hash = K2. Site registrations do not rotate this identity KEL.

2. Per-site branch

peerFactor = masterPassword + siteId (HTTP origins are normalized to scheme://host[:port]; app ids such as yadademo://app are kept as-is).

kp0 = derive(K0, peerFactor)
kp1 = derive(kp0, peerFactor)
kp2 = derive(kp1, peerFactor)
…

Register writes off-chain counter 0 (kp0 → kp1) then counter 1 (kp1 → kp2). Each later sign-in appends counter n+1. The signer’s public_key_hash must equal the previous prerotated_key_hash.

Register resets any existing branch for that site id, then writes counters 0 and 1, so a re-register is a clean start.

3. Passwords (RP-side ratchet)

Passwords are deterministic: P_i = generateSitePassword(kp_i, siteId, i). Hashes sent to the RP are PHC strings (default $pbkdf2-sha256$i=310000$…). Salt is SHA-256("yada-phc-salt-v1|" + password)[0:16] so the same password always produces the same PHC (needed for string compare on the harness). Also supported: argon2id, scrypt, bcrypt.

EventAuthenticator returnsRP action
RegisterP0 and H(P1)Store H(P1) as next hash
Sign-inP1 and H(P2)Verify P1 against stored H(P1); store H(P2)

A production RP would POST password + nextPasswordHash to its own backend instead of verifying in the WebView. The Yada node is unchanged.

4. What the node checks

POST /password-rotation/verify still exists for password-gated advances; the current clients do not use it for sign-in.

5. Resync

If mempool/off-chain tips disappear, Resync rebuilds site keys from K0 to the node tip and drops branches whose inception key no longer matches. Identity inception is recovered from /key-event-log?public_key= (not /has-key-event-log, which misses K0).

Node endpoints

MethodPathPurpose
POST/transactionIdentity inception (on-chain)
POST/password-rotation/offchainAppend site-branch KEL step
GET/password-rotation/offchain/tip?branch_peer=Latest step for a site
POST/password-rotation/offchain/resetDelete a site branch
GET/key-event-log?public_key=Identity KEL (inception detection)

Client transports

ClientHow the RP is invokedSite id
Extension + harnesspostMessage YADA_PASSWORD_REGISTER / YADA_PASSWORD_SIGNINhttps://host:port origin
Android Yada Password + Auth Demoyadapass://auth?… then callback yadademo://result?…yadademo://app

Both authenticators use @yadacoin/password-core (registerSite, rotateSitePassword, hashPassword). The RP either compares passwordHash === storedNext (harness) or verifyPassword(password, storedNext) (demo). With deterministic PHC salts those checks are equivalent.