@dotrino/lobby 0.4.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +0 -0
- package/README.md +0 -0
- package/package.json +2 -2
- package/src/engine.js +0 -0
- package/src/index.d.ts +0 -0
- package/src/index.js +0 -0
- package/src/lobby.js +0 -0
- package/src/protocol.js +0 -0
- package/src/reputation.js +29 -8
- package/src/room.js +29 -15
- package/src/transport.js +0 -0
- package/src/util.js +0 -0
package/LICENSE
CHANGED
|
File without changes
|
package/README.md
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotrino/lobby",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Lobby + matchmaking reciclable para juegos del ecosistema Dotrino: salas, asientos, espectadores, motor de turnos autoritativo, reputación y contactos sobre el proxy/identity/reputation compartidos",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
],
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"@dotrino/identity": ">=0.5.0",
|
|
36
|
-
"@dotrino/proxy-client": "
|
|
36
|
+
"@dotrino/proxy-client": ">=0.13.1",
|
|
37
37
|
"@dotrino/reputation": ">=0.3.0"
|
|
38
38
|
},
|
|
39
39
|
"peerDependenciesMeta": {
|
package/src/engine.js
CHANGED
|
File without changes
|
package/src/index.d.ts
CHANGED
|
File without changes
|
package/src/index.js
CHANGED
|
File without changes
|
package/src/lobby.js
CHANGED
|
File without changes
|
package/src/protocol.js
CHANGED
|
File without changes
|
package/src/reputation.js
CHANGED
|
@@ -68,21 +68,42 @@ export async function rankRooms (rooms, { reputation, contacts, preferContacts =
|
|
|
68
68
|
/** Payload canónico que ambos jugadores firman (debe coincidir con el server). */
|
|
69
69
|
export function receiptPayload (a, b, ts) { return { op: 'receipt', a, b, ts } }
|
|
70
70
|
|
|
71
|
-
/**
|
|
71
|
+
/**
|
|
72
|
+
* Firma MI mitad, y devuelve el paquete entero: `{ sig, signer, chain }`.
|
|
73
|
+
*
|
|
74
|
+
* Antes devolvía solo la firma, y con eso el registro no puede comprobar nada: firma el
|
|
75
|
+
* APARATO y el recibo (o el evento) es entre dos PERSONAS, así que hace falta la cadena que
|
|
76
|
+
* dice que ese aparato habla por esa identidad. Sin ella, una partida jugada desde el
|
|
77
|
+
* teléfono no contaba — daba «recibo inválido» o «co-firma inválida».
|
|
78
|
+
*/
|
|
79
|
+
async function firmarMitad (identity, payload) {
|
|
80
|
+
const signed = await identity.signData(payload)
|
|
81
|
+
if (typeof signed === 'string') {
|
|
82
|
+
throw new Error('dotrino-lobby: signData debe devolver { signature, publickey, chain } (vault ≥ 0.84)')
|
|
83
|
+
}
|
|
84
|
+
if (!Array.isArray(signed.chain) || !signed.chain.length) {
|
|
85
|
+
throw new Error('dotrino-lobby: la firma vino sin cadena; el registro no podría comprobar quién firma por ti')
|
|
86
|
+
}
|
|
87
|
+
return { sig: signed.signature, signer: signed.publickey, chain: signed.chain }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Firma mi mitad del recibo con el vault. → `{ sig, signer, chain }`. */
|
|
72
91
|
export async function signReceiptHalf (identity, a, b, ts) {
|
|
73
|
-
|
|
74
|
-
return typeof signed === 'string' ? signed : signed.signature
|
|
92
|
+
return firmarMitad(identity, receiptPayload(a, b, ts))
|
|
75
93
|
}
|
|
76
94
|
|
|
77
95
|
// ── Evento de indicador derivado co-firmado (p.ej. ELO) ────────────
|
|
78
96
|
/** Payload canónico del evento que ambas partes firman (debe coincidir con el
|
|
79
97
|
* server de reputation: {op:'event', indicator, scope, a, b, outcome, ts}). */
|
|
80
|
-
export function eventPayload (indicator, scope, a, b, outcome, ts) {
|
|
98
|
+
export function eventPayload (indicator, scope, a, b, outcome, ts, aud) {
|
|
99
|
+
// PARA QUIÉN es el evento va DENTRO de lo que firman los dos. Ponerlo al publicar no
|
|
100
|
+
// valdría de nada: la firma no lo cubriría, y el registro rechaza lo que no le nombra.
|
|
101
|
+
return { op: 'event', aud, indicator, scope, a, b, outcome, ts }
|
|
102
|
+
}
|
|
81
103
|
|
|
82
|
-
/** Firma mi mitad del evento con el vault. */
|
|
83
|
-
export async function signEventHalf (identity, indicator, scope, a, b, outcome, ts) {
|
|
84
|
-
|
|
85
|
-
return typeof signed === 'string' ? signed : signed.signature
|
|
104
|
+
/** Firma mi mitad del evento con el vault. → `{ sig, signer, chain }`. */
|
|
105
|
+
export async function signEventHalf (identity, indicator, scope, a, b, outcome, ts, aud) {
|
|
106
|
+
return firmarMitad(identity, eventPayload(indicator, scope, a, b, outcome, ts, aud))
|
|
86
107
|
}
|
|
87
108
|
|
|
88
109
|
/**
|
package/src/room.js
CHANGED
|
@@ -373,7 +373,10 @@ export class Room extends Emitter {
|
|
|
373
373
|
_hostReceiptSign (from, d) {
|
|
374
374
|
const rec = this._pendingReceiptSig.get(d.receiptId)
|
|
375
375
|
if (!rec || from !== rec.token) return // sólo el destinatario del recibo puede co-firmarlo
|
|
376
|
-
|
|
376
|
+
// Sin la mitad completa del otro no hay recibo: se descarta en vez de guardar uno que
|
|
377
|
+
// el registro va a rechazar (y que haría fallar la calificación entera).
|
|
378
|
+
if (!d.sig || !d.signer || !Array.isArray(d.chain) || !d.chain.length) { this._pendingReceiptSig.delete(d.receiptId); return }
|
|
379
|
+
const full = { a: rec.a, b: rec.b, ts: rec.ts, sigA: rec.sigA, signerA: rec.signerA, chainA: rec.chainA, sigB: d.sig, signerB: d.signer, chainB: d.chain }
|
|
377
380
|
this._receipts.set(rec.peerPubkey, full)
|
|
378
381
|
this._pendingReceiptSig.delete(d.receiptId)
|
|
379
382
|
this.emit('receipt', { pubkey: rec.peerPubkey, receipt: full })
|
|
@@ -604,10 +607,13 @@ export class Room extends Emitter {
|
|
|
604
607
|
try {
|
|
605
608
|
const a = this.myPubkey, b = s.pubkey
|
|
606
609
|
const ts = clock.now() + i // ts distinto por par → receiptId no adivinable entre peers
|
|
607
|
-
const
|
|
610
|
+
const mitadA = await signReceiptHalf(this.identity, a, b, ts)
|
|
608
611
|
const receiptId = `${ts}:${ids[i]}`
|
|
609
|
-
|
|
610
|
-
|
|
612
|
+
// La mitad viaja entera (firma + quién firmó + su cadena): el registro comprueba
|
|
613
|
+
// que ese aparato habla por ese jugador, no que la llave del jugador firmó.
|
|
614
|
+
const media = { sigA: mitadA.sig, signerA: mitadA.signer, chainA: mitadA.chain }
|
|
615
|
+
this._pendingReceiptSig.set(receiptId, { a, b, ts, ...media, peerPubkey: b, token: s.token })
|
|
616
|
+
this._sendTo(s.token, K.RECEIPT_OFFER, { receiptId, receipt: { a, b, ts, ...media } })
|
|
611
617
|
} catch (_) {}
|
|
612
618
|
}
|
|
613
619
|
}
|
|
@@ -628,11 +634,15 @@ export class Room extends Emitter {
|
|
|
628
634
|
const a = this.myPubkey, b = s.pubkey
|
|
629
635
|
const outcome = winnerPub ? (samePubkey(winnerPub, a) ? 'a' : (samePubkey(winnerPub, b) ? 'b' : 'draw')) : 'draw'
|
|
630
636
|
const ts = clock.now() + i
|
|
631
|
-
|
|
632
|
-
|
|
637
|
+
// El destinatario del registro va DENTRO de lo que firman los dos. Sin registro
|
|
638
|
+
// cableado no hay a quién publicar, así que no se co-firma nada.
|
|
639
|
+
const aud = this.reputation?.audience
|
|
640
|
+
if (!aud) continue
|
|
641
|
+
const data = eventPayload(indicator, scope, a, b, outcome, ts, aud)
|
|
642
|
+
const mitadA = await signEventHalf(this.identity, indicator, scope, a, b, outcome, ts, aud)
|
|
633
643
|
const resultId = `res:${ts}:${ids[i]}`
|
|
634
|
-
this._pendingResults.set(resultId, { data, sigA, token: s.token })
|
|
635
|
-
this._sendTo(s.token, K.RESULT_OFFER, { resultId, data, sigA })
|
|
644
|
+
this._pendingResults.set(resultId, { data, sigA: mitadA.sig, signerA: mitadA.signer, chainA: mitadA.chain, token: s.token })
|
|
645
|
+
this._sendTo(s.token, K.RESULT_OFFER, { resultId, data, sigA: mitadA.sig, signerA: mitadA.signer, chainA: mitadA.chain })
|
|
636
646
|
} catch (_) {}
|
|
637
647
|
}
|
|
638
648
|
}
|
|
@@ -641,7 +651,8 @@ export class Room extends Emitter {
|
|
|
641
651
|
const rec = this._pendingResults.get(d.resultId)
|
|
642
652
|
if (!rec || from !== rec.token) return // sólo el destinatario co-firma su resultado
|
|
643
653
|
this._pendingResults.delete(d.resultId)
|
|
644
|
-
|
|
654
|
+
if (!d.sig || !d.signer || !Array.isArray(d.chain) || !d.chain.length) return
|
|
655
|
+
const coSigned = { data: rec.data, sigA: rec.sigA, signerA: rec.signerA, chainA: rec.chainA, sigB: d.sig, signerB: d.signer, chainB: d.chain }
|
|
645
656
|
this.emit('result', { indicator: rec.data.indicator, scope: rec.data.scope, a: rec.data.a, b: rec.data.b, outcome: rec.data.outcome, coSigned })
|
|
646
657
|
}
|
|
647
658
|
|
|
@@ -664,9 +675,12 @@ export class Room extends Emitter {
|
|
|
664
675
|
// Anti-trampa: sólo co-firmo si el resultado del offer coincide con el que YO vi.
|
|
665
676
|
if (this._relativeWinner(a, b, this._public.result) !== outcome) return
|
|
666
677
|
try {
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
this.
|
|
678
|
+
// Se co-firma el `data` TAL CUAL vino, destinatario incluido: si el host propusiera
|
|
679
|
+
// otro registro, se firmaría para ese — así que se comprueba antes de firmar.
|
|
680
|
+
if (this.reputation?.audience && d.data.aud !== this.reputation.audience) return
|
|
681
|
+
const mitadB = await signEventHalf(this.identity, indicator, scope, a, b, outcome, ts, d.data.aud)
|
|
682
|
+
this._sendHost(K.RESULT_SIGN, { resultId: d.resultId, sig: mitadB.sig, signer: mitadB.signer, chain: mitadB.chain })
|
|
683
|
+
this.emit('result', { indicator, scope, a, b, outcome, coSigned: { data: d.data, sigA: d.sigA, signerA: d.signerA, chainA: d.chainA, sigB: mitadB.sig, signerB: mitadB.signer, chainB: mitadB.chain } })
|
|
670
684
|
} catch (_) {}
|
|
671
685
|
}
|
|
672
686
|
|
|
@@ -732,10 +746,10 @@ export class Room extends Emitter {
|
|
|
732
746
|
// ...y la contraparte tiene que ser el host real (pubkey conocido por el STATE).
|
|
733
747
|
if (!samePubkey(peer, this._public.hostPubkey)) return
|
|
734
748
|
try {
|
|
735
|
-
const
|
|
736
|
-
const full = { a, b, ts, sigA: d.receipt.sigA, sigB }
|
|
749
|
+
const mitadB = await signReceiptHalf(this.identity, a, b, ts)
|
|
750
|
+
const full = { a, b, ts, sigA: d.receipt.sigA, signerA: d.receipt.signerA, chainA: d.receipt.chainA, sigB: mitadB.sig, signerB: mitadB.signer, chainB: mitadB.chain }
|
|
737
751
|
this._receipts.set(peer, full)
|
|
738
|
-
this._sendHost(K.RECEIPT_SIGN, { receiptId: d.receiptId, sig:
|
|
752
|
+
this._sendHost(K.RECEIPT_SIGN, { receiptId: d.receiptId, sig: mitadB.sig, signer: mitadB.signer, chain: mitadB.chain })
|
|
739
753
|
this.emit('receipt', { pubkey: peer, receipt: full })
|
|
740
754
|
} catch (_) {}
|
|
741
755
|
}
|
package/src/transport.js
CHANGED
|
File without changes
|
package/src/util.js
CHANGED
|
File without changes
|