Skip to content

Commit f50730d

Browse files
harden: reject identity points on the wire and guard the zero secret-scalar
1 parent d051768 commit f50730d

3 files changed

Lines changed: 20 additions & 5 deletions

File tree

src/group.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export interface Pt {
1212
negate(): Pt
1313
equals(other: Pt): boolean
1414
toBytes(): Uint8Array
15+
is0(): boolean
1516
}
1617

1718
/** The fixed generator g1. */

src/smp.security.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,12 @@ describe('smp security', () => {
6262
const badMsg2 = new Uint8Array(352).fill(0xff) // all-0xff is not a valid encoded point
6363
expect(() => A.session.next(badMsg2)).toThrow(SmpError)
6464
})
65+
66+
it('rejects an identity point on the wire (SmpError)', () => {
67+
const A = initiate('secret', enc('chan'))
68+
const B = respond('secret', enc('chan'))
69+
const m = A.first.slice()
70+
m.fill(0, 0, 32) // 32 zero bytes = the ristretto identity, in the g2a slot
71+
expect(() => B.session.next(m)).toThrow(SmpError)
72+
})
6573
})

src/smp.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,19 @@ export interface Msg2 { g2b: Pt; g3b: Pt; Pb: Pt; Qb: Pt; pokB2: PoK; pokB3: PoK
3232
export interface Msg3 { Pa: Pt; Qa: Pt; Ra: Pt; reprA: Repr; eqRa: EqualLogs }
3333
export interface Msg4 { Rb: Pt; eqRb: EqualLogs }
3434

35+
function nonIdentity(p: Pt): Pt {
36+
if (p.is0()) throw new SmpError('identity point rejected')
37+
return p
38+
}
39+
3540
// msg1: g2a, g3a, pok2{c,s}, pok3{c,s} → 2 points + 4 scalars = 192 B
3641
export function encodeMsg1(m: Msg1): Uint8Array {
3742
return new Writer().point(m.g2a).point(m.g3a)
3843
.scalar(m.pok2.c).scalar(m.pok2.s).scalar(m.pok3.c).scalar(m.pok3.s).bytes()
3944
}
4045
export function decodeMsg1(b: Uint8Array): Msg1 {
4146
const r = new Reader(b, 192)
42-
const g2a = r.point(), g3a = r.point()
47+
const g2a = nonIdentity(r.point()), g3a = nonIdentity(r.point())
4348
const pok2 = { c: r.scalar(), s: r.scalar() }, pok3 = { c: r.scalar(), s: r.scalar() }
4449
return { g2a, g3a, pok2, pok3 }
4550
}
@@ -52,7 +57,7 @@ export function encodeMsg2(m: Msg2): Uint8Array {
5257
}
5358
export function decodeMsg2(b: Uint8Array): Msg2 {
5459
const r = new Reader(b, 352)
55-
const g2b = r.point(), g3b = r.point(), Pb = r.point(), Qb = r.point()
60+
const g2b = nonIdentity(r.point()), g3b = nonIdentity(r.point()), Pb = nonIdentity(r.point()), Qb = nonIdentity(r.point())
5661
const pokB2 = { c: r.scalar(), s: r.scalar() }
5762
const pokB3 = { c: r.scalar(), s: r.scalar() }
5863
const reprB = { c: r.scalar(), sr: r.scalar(), sy: r.scalar() }
@@ -67,7 +72,7 @@ export function encodeMsg3(m: Msg3): Uint8Array {
6772
}
6873
export function decodeMsg3(b: Uint8Array): Msg3 {
6974
const r = new Reader(b, 256)
70-
const Pa = r.point(), Qa = r.point(), Ra = r.point()
75+
const Pa = nonIdentity(r.point()), Qa = nonIdentity(r.point()), Ra = nonIdentity(r.point())
7176
const reprA = { c: r.scalar(), sr: r.scalar(), sy: r.scalar() }
7277
const eqRa = { c: r.scalar(), s: r.scalar() }
7378
return { Pa, Qa, Ra, reprA, eqRa }
@@ -79,7 +84,7 @@ export function encodeMsg4(m: Msg4): Uint8Array {
7984
}
8085
export function decodeMsg4(b: Uint8Array): Msg4 {
8186
const r = new Reader(b, 96)
82-
const Rb = r.point()
87+
const Rb = nonIdentity(r.point())
8388
const eqRb = { c: r.scalar(), s: r.scalar() }
8489
return { Rb, eqRb }
8590
}
@@ -88,7 +93,8 @@ const DOM_SECRET = new TextEncoder().encode('private-equality/secret-v1')
8893

8994
function secretToScalar(secret: Secret): bigint {
9095
const bytes = typeof secret === 'string' ? new TextEncoder().encode(secret) : secret
91-
return hashToScalar(DOM_SECRET, bytes)
96+
const s = hashToScalar(DOM_SECRET, bytes)
97+
return s === 0n ? 1n : s
9298
}
9399
function toBindingHash(sessionBinding: Uint8Array): Uint8Array {
94100
return sha256(sessionBinding) // fixed 32 bytes for unambiguous transcripts

0 commit comments

Comments
 (0)