// Copyright 2025 The BoringSSL Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package spake2plus implements RFC 9383 for testing. package spake2plus import ( "bytes" "crypto/elliptic" "crypto/hkdf" "crypto/hmac" "crypto/rand" "crypto/sha256" "encoding/binary" "errors" "io" "math/big" "golang.org/x/crypto/scrypt" ) const ( verifierSize = 32 // size of w0, w1 in bytes, for P-256 registrationRecordSize = 65 // uncompressed P-256 point size shareSize = 65 confirmSize = 32 keySize = 32 pbkdfOutputSize = 80 ) type Role int const ( RoleProver Role = iota RoleVerifier ) type state int const ( stateInit state = iota stateShareGenerated stateKeyGenerated ) type Context struct { IDProver []byte IDVerifier []byte Role Role curve elliptic.Curve context []byte w0 *big.Int w1 *big.Int Lx, Ly *big.Int // L point Mx, My *big.Int // M point Nx, Ny *big.Int // N point Xx, Xy *big.Int // X point Yx, Yy *big.Int // Y point Zx, Zy *big.Int // Z point Vx, Vy *big.Int // V point x *big.Int // ephemeral scalar for prover y *big.Int // ephemeral scalar for verifier share []byte confirm []byte state state } // Hardcoded M and N from the RFC (uncompressed) var kM = []byte{ 0x04, 0x88, 0x6e, 0x2f, 0x97, 0xac, 0xe4, 0x6e, 0x55, 0xba, 0x9d, 0xd7, 0x24, 0x25, 0x79, 0xf2, 0x99, 0x3b, 0x64, 0xe1, 0x6e, 0xf3, 0xdc, 0xab, 0x95, 0xaf, 0xd4, 0x97, 0x33, 0x3d, 0x8f, 0xa1, 0x2f, 0x5f, 0xf3, 0x55, 0x16, 0x3e, 0x43, 0xce, 0x22, 0x4e, 0x0b, 0x0e, 0x65, 0xff, 0x02, 0xac, 0x8e, 0x5c, 0x7b, 0xe0, 0x94, 0x19, 0xc7, 0x85, 0xe0, 0xca, 0x54, 0x7d, 0x55, 0xa1, 0x2e, 0x2d, 0x20, } var kN = []byte{ 0x04, 0xd8, 0xbb, 0xd6, 0xc6, 0x39, 0xc6, 0x29, 0x37, 0xb0, 0x4d, 0x99, 0x7f, 0x38, 0xc3, 0x77, 0x07, 0x19, 0xc6, 0x29, 0xd7, 0x01, 0x4d, 0x49, 0xa2, 0x4b, 0x4f, 0x98, 0xba, 0xa1, 0x29, 0x2b, 0x49, 0x07, 0xd6, 0x0a, 0xa6, 0xbf, 0xad, 0xe4, 0x50, 0x08, 0xa6, 0x36, 0x33, 0x7f, 0x51, 0x68, 0xc6, 0x4d, 0x9b, 0xd3, 0x60, 0x34, 0x80, 0x8c, 0xd5, 0x64, 0x49, 0x0b, 0x1e, 0x65, 0x6e, 0xdb, 0xe7, } func Register( pw []byte, idProver []byte, idVerifier []byte, ) (pwVerifierW0 []byte, pwVerifierW1 []byte, registrationRecord []byte, err error) { mhfBuf := new(bytes.Buffer) if err := binary.Write(mhfBuf, binary.LittleEndian, uint64(len(pw))); err != nil { return nil, nil, nil, err } mhfBuf.Write(pw) if err := binary.Write(mhfBuf, binary.LittleEndian, uint64(len(idProver))); err != nil { return nil, nil, nil, err } mhfBuf.Write(idProver) if err := binary.Write(mhfBuf, binary.LittleEndian, uint64(len(idVerifier))); err != nil { return nil, nil, nil, err } mhfBuf.Write(idVerifier) key, err := scrypt.Key(mhfBuf.Bytes(), nil, 32768, 8, 1, pbkdfOutputSize) if err != nil { return nil, nil, nil, err } curve := elliptic.P256() N := curve.Params().N w0 := new(big.Int).SetBytes(key[:pbkdfOutputSize/2]) w0.Mod(w0, N) w1 := new(big.Int).SetBytes(key[pbkdfOutputSize/2:]) w1.Mod(w1, N) pwVerifierW0 = make([]byte, verifierSize) pwVerifierW1 = make([]byte, verifierSize) copy(pwVerifierW0, w0.Bytes()) copy(pwVerifierW1, w1.Bytes()) Lx, Ly := curve.ScalarBaseMult(w1.Bytes()) L := elliptic.Marshal(curve, Lx, Ly) registrationRecord = make([]byte, registrationRecordSize) copy(registrationRecord, L) return pwVerifierW0, pwVerifierW1, registrationRecord, nil } func newContext( role Role, context []byte, idProver []byte, idVerifier []byte, pwVerifierW0 []byte, pwVerifierW1 []byte, registrationRecord []byte, x *big.Int, y *big.Int, ) (*Context, error) { curve := elliptic.P256() Mx, My := elliptic.Unmarshal(curve, kM) if Mx == nil { return nil, errors.New("invalid M point") } Nx, Ny := elliptic.Unmarshal(curve, kN) if Nx == nil { return nil, errors.New("invalid N point") } ctx := &Context{ Role: role, curve: curve, context: append([]byte(nil), context...), IDProver: append([]byte(nil), idProver...), IDVerifier: append([]byte(nil), idVerifier...), Mx: Mx, My: My, Nx: Nx, Ny: Ny, state: stateInit, } N := curve.Params().N if role == RoleProver { if pwVerifierW0 == nil || pwVerifierW1 == nil || y != nil { return nil, errors.New("invalid parameters for prover") } ctx.w0 = new(big.Int).SetBytes(pwVerifierW0) ctx.w1 = new(big.Int).SetBytes(pwVerifierW1) ctx.w0.Mod(ctx.w0, N) ctx.w1.Mod(ctx.w1, N) if x == nil { xRand, err := randFieldElement(curve) if err != nil { return nil, err } ctx.x = xRand } else { ctx.x = new(big.Int).Set(x) } } else { // Verifier if pwVerifierW0 == nil || registrationRecord == nil || x != nil { return nil, errors.New("invalid parameters for verifier") } ctx.w0 = new(big.Int).SetBytes(pwVerifierW0) ctx.w0.Mod(ctx.w0, N) // Load L Lx, Ly := elliptic.Unmarshal(curve, registrationRecord) if Lx == nil { return nil, errors.New("invalid L point") } ctx.Lx, ctx.Ly = Lx, Ly if y == nil { yRand, err := randFieldElement(curve) if err != nil { return nil, err } ctx.y = yRand } else { ctx.y = new(big.Int).Set(y) } } return ctx, nil } func randFieldElement(curve elliptic.Curve) (*big.Int, error) { params := curve.Params() b := make([]byte, (params.BitSize+7)/8) var k *big.Int for { if _, err := rand.Read(b); err != nil { return nil, err } k = new(big.Int).SetBytes(b) if k.Sign() != 0 && k.Cmp(params.N) < 0 { break } } return k, nil } func NewProver( context []byte, idProver []byte, idVerifier []byte, pwVerifierW0 []byte, pwVerifierW1 []byte, ) (*Context, error) { return newContext(RoleProver, context, idProver, idVerifier, pwVerifierW0, pwVerifierW1, nil, nil, nil) } func NewVerifier( context []byte, idProver []byte, idVerifier []byte, pwVerifierW0 []byte, registrationRecord []byte, ) (*Context, error) { return newContext(RoleVerifier, context, idProver, idVerifier, pwVerifierW0, nil, registrationRecord, nil, nil) } func (ctx *Context) GenerateProverShare() (share []byte, err error) { if ctx.Role != RoleProver { return nil, errors.New("invalid state for prover share generation") } if ctx.state != stateInit { return ctx.share, nil } curve := ctx.curve // l = x * G lx, ly := curve.ScalarBaseMult(ctx.x.Bytes()) // r = w0 * M rx, ry := curve.ScalarMult(ctx.Mx, ctx.My, ctx.w0.Bytes()) // X = l + r Xx, Xy := curve.Add(lx, ly, rx, ry) ctx.Xx, ctx.Xy = Xx, Xy share = elliptic.Marshal(curve, Xx, Xy) ctx.share = append([]byte(nil), share...) ctx.state = stateShareGenerated return share, nil } func updateWithLengthPrefix(h io.Writer, data []byte) { var lenLe [8]byte binary.LittleEndian.PutUint64(lenLe[:], uint64(len(data))) h.Write(lenLe[:]) h.Write(data) } func computeTranscriptAndConfirmation( ctx *Context, shareP, shareV []byte, ) (proverConfirm, verifierConfirm, sharedSecret []byte, err error) { curve := ctx.curve Z := elliptic.Marshal(curve, ctx.Zx, ctx.Zy) V := elliptic.Marshal(curve, ctx.Vx, ctx.Vy) h := sha256.New() updateWithLengthPrefix(h, ctx.context) updateWithLengthPrefix(h, ctx.IDProver) updateWithLengthPrefix(h, ctx.IDVerifier) updateWithLengthPrefix(h, kM) updateWithLengthPrefix(h, kN) updateWithLengthPrefix(h, shareP) updateWithLengthPrefix(h, shareV) updateWithLengthPrefix(h, Z) updateWithLengthPrefix(h, V) updateWithLengthPrefix(h, ctx.w0.Bytes()) K_main := h.Sum(nil) confirmationStr := []byte("ConfirmationKeys") keys := doHKDF(K_main, confirmationStr, keySize*2) secretInfoStr := []byte("SharedKey") sharedSecret = doHKDF(K_main, secretInfoStr, keySize) // Prover confirmation = HMAC(keys[:32], shareV) macP := hmac.New(sha256.New, keys[:keySize]) macP.Write(shareV) proverConfirm = macP.Sum(nil) // Verifier confirmation = HMAC(keys[32:], shareP) macV := hmac.New(sha256.New, keys[keySize:]) macV.Write(shareP) verifierConfirm = macV.Sum(nil) return } func doHKDF(ikm, info []byte, size int) []byte { out, err := hkdf.Key(sha256.New, ikm, nil, string(info), size) if err != nil { panic(err) } return out } func (ctx *Context) ProcessProverShare( proverShare []byte, ) (verifierShare []byte, verifierConfirm []byte, sharedSecret []byte, err error) { if ctx.Role != RoleVerifier || ctx.state != stateInit || len(proverShare) != shareSize { return nil, nil, nil, errors.New("invalid state or share") } curve := ctx.curve // Y = y*G + w0*N lx, ly := curve.ScalarBaseMult(ctx.y.Bytes()) rx, ry := curve.ScalarMult(ctx.Nx, ctx.Ny, ctx.w0.Bytes()) Yx, Yy := curve.Add(lx, ly, rx, ry) ctx.Yx, ctx.Yy = Yx, Yy verifierShare = elliptic.Marshal(curve, Yx, Yy) if px, py := elliptic.Unmarshal(curve, proverShare); px == nil { return nil, nil, nil, errors.New("invalid prover share") } else { ctx.Xx, ctx.Xy = px, py } // T = X - w0*M mx, my := curve.ScalarMult(ctx.Mx, ctx.My, ctx.w0.Bytes()) mx, my = mx, new(big.Int).Neg(my) my.Mod(my, curve.Params().P) Tx, Ty := curve.Add(ctx.Xx, ctx.Xy, mx, my) // Z = (y)*T Zx, Zy := curve.ScalarMult(Tx, Ty, ctx.y.Bytes()) ctx.Zx, ctx.Zy = Zx, Zy // V = (y)*L Vx, Vy := curve.ScalarMult(ctx.Lx, ctx.Ly, ctx.y.Bytes()) ctx.Vx, ctx.Vy = Vx, Vy proverConfirm, verifierConfirm, sharedSecret, err := computeTranscriptAndConfirmation(ctx, proverShare, verifierShare) if err != nil { return nil, nil, nil, err } ctx.confirm = proverConfirm ctx.state = stateKeyGenerated return verifierShare, verifierConfirm, sharedSecret, nil } // computeProverConfirmation (Prover side) func (ctx *Context) ComputeProverConfirmation( verifierShare []byte, claimedVerifierConfirm []byte, ) (proverConfirm []byte, sharedSecret []byte, err error) { if ctx.Role != RoleProver || ctx.state != stateShareGenerated || len(verifierShare) != shareSize || len(claimedVerifierConfirm) != confirmSize { return nil, nil, errors.New("invalid state or input") } curve := ctx.curve vx, vy := elliptic.Unmarshal(curve, verifierShare) if vx == nil { return nil, nil, errors.New("invalid verifier share") } ctx.Yx, ctx.Yy = vx, vy // T = Y - w0*N nx, ny := curve.ScalarMult(ctx.Nx, ctx.Ny, ctx.w0.Bytes()) ny.Neg(ny) ny.Mod(ny, curve.Params().P) Tx, Ty := curve.Add(ctx.Yx, ctx.Yy, nx, ny) // Z = x*T Zx, Zy := curve.ScalarMult(Tx, Ty, ctx.x.Bytes()) ctx.Zx, ctx.Zy = Zx, Zy // V = w1*T Vx, Vy := curve.ScalarMult(Tx, Ty, ctx.w1.Bytes()) ctx.Vx, ctx.Vy = Vx, Vy // Compute transcript proverConfirm, verifierConfirm, sharedSecret, err := computeTranscriptAndConfirmation(ctx, ctx.share, verifierShare) if err != nil { return nil, nil, err } // Check verifier confirm if !hmac.Equal(verifierConfirm, claimedVerifierConfirm) { return nil, nil, errors.New("verifier confirmation mismatch") } ctx.state = stateKeyGenerated return proverConfirm, sharedSecret, nil } // VerifyProverConfirmation (Verifier side) func (ctx *Context) VerifyProverConfirmation(proverConfirm []byte) error { if ctx.Role != RoleVerifier || ctx.state != stateKeyGenerated || len(proverConfirm) != confirmSize { return errors.New("invalid state or input") } if !hmac.Equal(ctx.confirm, proverConfirm) { return errors.New("prover confirmation mismatch") } return nil }