Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
6e7613f
MF-L01: fix(contracts/ChannelHub): cap ERC20 transfer returndata copy…
nksazonov May 8, 2026
0cd7998
MF-H01: fix(nitronode): paginate get_last_key_states endpoints (#724)
philanton May 8, 2026
14fd16d
MF-I01-I02: fix(contracts): address security audit findings I-01 and …
nksazonov May 8, 2026
e2f12b8
MF-C01: rpc: cap inbound WebSocket frame size and rate-limit per conn…
philanton May 8, 2026
558b58b
MF-L02: docs(protocol): qualify enforcement guarantee for intent-spec…
nksazonov May 11, 2026
c5de723
MF-L02-I03-I04_I05: fix(contracts): add more Node trust assumptions a…
nksazonov May 11, 2026
e84f227
MF-M01: backfill state user_sig from on-chain events (#731)
philanton May 11, 2026
8b4ad82
MF-M02: fix(rpc): release Serve wait group on processSink overflow (#…
philanton May 11, 2026
2d21b00
MF-I06: fix(nitronode): gate escrow transitions on home channel oncha…
philanton May 12, 2026
769bd79
MF-M05: fix(nitronode): enforce TLS by default for Postgres (#733)
philanton May 12, 2026
97871b0
MF-M07: Unblock receiver states after finalized escrow operations (#735)
philanton May 12, 2026
0c02a45
MF-M04: feat: provide tooling for and enhance docs on ValidatorRegist…
nksazonov May 13, 2026
07cc1dc
MF-L04: fix(contracts): reject redundant native value (#741)
ihsraham May 13, 2026
32bdc51
MF-H02: bind session key registration to a single owner per kind (#739)
philanton May 13, 2026
0cbb9f8
MF-I07: fix(contracts): enforce max challenge duration (#752)
ihsraham May 13, 2026
aaac790
MF-M08: fix(rpc): replace Origin label with application_id on connect…
philanton May 13, 2026
38d00d3
MF-C02: fix(core): add ChannelStatusClosing to gate post-finalize sta…
nksazonov May 13, 2026
36c9832
MF-L06: fix(contracts): clear stale challengeExpireAt on cooperative …
nksazonov May 13, 2026
29641e8
MF-I08: docs: document ChannelClosed event orientation ambiguity duri…
nksazonov May 13, 2026
97f6f8b
MF-M09: fix(nitronode): auto-challenge home channel on withheld escro…
philanton May 13, 2026
7aa8265
MF-L09: fix(nitronode): validate parsed app session nonce (#751)
philanton May 13, 2026
2587d99
MF-L05: docs(contracts): document informational events not guaranteed…
nksazonov May 13, 2026
0e4612f
MF-L08: fix(nitronode/api): default get_last_key_states to active-onl…
philanton May 13, 2026
3234473
Merge branch 'main' into fix/audit-findings-final
philanton May 13, 2026
ba58e89
test(sdk/ts): refresh public API snapshot for ChannelStatus.Closing
philanton May 13, 2026
6152e74
fix(scripts/drift): rename smoke caller from getLastKeyStates to getL…
philanton May 13, 2026
af32b71
MF-L10: fix: emit escrowIds array in EscrowDepositsPurged event and h…
nksazonov May 13, 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
98 changes: 78 additions & 20 deletions cerebro/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -1010,13 +1010,45 @@ func (o *Operator) createChannelSessionKey(ctx context.Context, sessionKeyAddr,
return
}

// Determine version by fetching existing keys
// Determine version by fetching existing keys. include_inactive=true so an expired
// prior version still surfaces — otherwise rotation would restart from version 1 and
// collide with the monotonic pointer enforced server-side.
includeInactive := true
var version uint64 = 1
existingStates, err := o.client.GetLastChannelKeyStates(ctx, wallet, &sdk.GetLastChannelKeyStatesOptions{
SessionKey: &sessionKeyAddr,
SessionKey: &sessionKeyAddr,
IncludeInactive: &includeInactive,
})
if err == nil && len(existingStates) > 0 {
version = existingStates[0].Version + 1
for _, s := range existingStates {
if s.Version >= version {
version = s.Version + 1
}
}
}

// SessionKeySig requires the session-key private key. Fetch it up-front and bail if the
// stored key doesn't match the address being registered — without the private key, the
// nitronode rejects the submit.
storedPK, pkErr := o.store.GetSessionKeyPrivateKey()
if pkErr != nil {
fmt.Printf("ERROR: Cannot register session key without the matching private key: %v\n", pkErr)
return
}
storedRawSigner, sigErr := sign.NewEthereumRawSigner(storedPK)
if sigErr != nil {
fmt.Printf("ERROR: Failed to load stored session key: %v\n", sigErr)
return
}
if !strings.EqualFold(storedRawSigner.PublicKey().Address().String(), sessionKeyAddr) {
fmt.Printf("ERROR: Stored session key %s does not match the address being registered (%s)\n",
storedRawSigner.PublicKey().Address().String(), sessionKeyAddr)
return
}
sessionKeySigner, sigErr := sign.NewEthereumMsgSignerFromRaw(storedRawSigner)
if sigErr != nil {
fmt.Printf("ERROR: Failed to construct session-key message signer: %v\n", sigErr)
return
}

expiresAt := time.Now().Add(time.Duration(expiresHours) * time.Hour)
Expand All @@ -1037,6 +1069,13 @@ func (o *Operator) createChannelSessionKey(ctx context.Context, sessionKeyAddr,
}
state.UserSig = sig

keySig, err := sdk.SignChannelSessionKeyOwnership(state, sessionKeySigner)
if err != nil {
fmt.Printf("ERROR: Failed to sign session key ownership: %v\n", err)
return
}
state.SessionKeySig = keySig

fmt.Println("Submitting channel session key state...")
if err := o.client.SubmitChannelSessionKeyState(ctx, state); err != nil {
fmt.Printf("ERROR: Failed to submit session key state: %v\n", err)
Expand All @@ -1049,21 +1088,7 @@ func (o *Operator) createChannelSessionKey(ctx context.Context, sessionKeyAddr,
fmt.Printf(" Assets: %s\n", strings.Join(assets, ", "))
fmt.Printf(" Expires At: %s\n", expiresAt.Format("2006-01-02 15:04:05"))

// If we have a stored session key matching this address, activate it as the state signer
storedPK, pkErr := o.store.GetSessionKeyPrivateKey()
if pkErr != nil {
return
}
storedSigner, sigErr := sign.NewEthereumRawSigner(storedPK)
if sigErr != nil {
return
}
if !strings.EqualFold(storedSigner.PublicKey().Address().String(), sessionKeyAddr) {
return
}

// Compute metadata hash and store full session key data
metadataHash, err := core.GetChannelSessionKeyAuthMetadataHashV1(version, assets, expiresAt.Unix())
metadataHash, err := core.GetChannelSessionKeyAuthMetadataHashV1(wallet, version, assets, expiresAt.Unix())
if err != nil {
fmt.Printf("WARNING: Failed to compute metadata hash: %v\n", err)
return
Expand Down Expand Up @@ -1135,10 +1160,14 @@ func (o *Operator) createAppSessionKey(ctx context.Context, sessionKeyAddr, expi
return
}

// Determine version by fetching existing keys
// Determine version by fetching existing keys. include_inactive=true so an expired
// prior version still surfaces — otherwise rotation would restart from version 1 and
// collide with the monotonic pointer enforced server-side.
includeInactive := true
var version uint64 = 1
existingStates, err := o.client.GetLastAppKeyStates(ctx, wallet, &sdk.GetLastKeyStatesOptions{
SessionKey: &sessionKeyAddr,
SessionKey: &sessionKeyAddr,
IncludeInactive: &includeInactive,
})
if err == nil && len(existingStates) > 0 {
for _, s := range existingStates {
Expand All @@ -1148,6 +1177,28 @@ func (o *Operator) createAppSessionKey(ctx context.Context, sessionKeyAddr, expi
}
}

// SessionKeySig requires the session-key private key.
storedPK, pkErr := o.store.GetSessionKeyPrivateKey()
if pkErr != nil {
fmt.Printf("ERROR: Cannot register session key without the matching private key: %v\n", pkErr)
return
}
storedRawSigner, sigErr := sign.NewEthereumRawSigner(storedPK)
if sigErr != nil {
fmt.Printf("ERROR: Failed to load stored session key: %v\n", sigErr)
return
}
if !strings.EqualFold(storedRawSigner.PublicKey().Address().String(), sessionKeyAddr) {
fmt.Printf("ERROR: Stored session key %s does not match the address being registered (%s)\n",
storedRawSigner.PublicKey().Address().String(), sessionKeyAddr)
return
}
sessionKeySigner, sigErr := sign.NewEthereumMsgSignerFromRaw(storedRawSigner)
if sigErr != nil {
fmt.Printf("ERROR: Failed to construct session-key message signer: %v\n", sigErr)
return
}

state := app.AppSessionKeyStateV1{
UserAddress: wallet,
SessionKey: sessionKeyAddr,
Expand All @@ -1165,6 +1216,13 @@ func (o *Operator) createAppSessionKey(ctx context.Context, sessionKeyAddr, expi
}
state.UserSig = sig

keySig, err := sdk.SignAppSessionKeyOwnership(state, sessionKeySigner)
if err != nil {
fmt.Printf("ERROR: Failed to sign session key ownership: %v\n", err)
return
}
state.SessionKeySig = keySig

fmt.Println("Submitting app session key state...")
if err := o.client.SubmitAppSessionKeyState(ctx, state); err != nil {
fmt.Printf("ERROR: Failed to submit session key state: %v\n", err)
Expand Down
Loading
Loading