¿Las firmas Schnorr estandarizadas de sipa son incompatibles con las firmas ciegas?

Aquí, sipa propone una estandarización de las firmas de schnorr: https://github.com/sipa/bips/blob/bip-schnorr/bip-schnorr.mediawiki

Por ejemplo, sign()devuelve Sig{R,s}que se pueden codificar con 64 bytes (Rx || s) donde Ry se puede desambiguar a través de un residuo cuadrático.

Después de implementar sign()y verify()de acuerdo con ese documento, intenté implementar firmas ciegas.

Sin embargo, la verificación de mi firma no cegada falla el 50 % de las veces debido a la línea return false if jacobi(R.y) !== 1en verify(), adjunta a continuación.

Aquí está mi pseudocódigo impl:

type Signature = { Rx: int, s: int }
type Unblinder = { alpha: int, Rx: int }
type BlindedMessage = { challenge: int }
type BlindedSignature = { s: int }

fn blindMessage(nonce: Point, signer: Point, message: bytes): (Unblinder, BlindedMessage) {
    R = nonce
    P = signer
    alpha = rand()
    beta = rand()
    R' = R + alpha*G + beta*P
    // challenge
    c' = int(hash(R'.x || P || message)) % curve.n
    // blinded challenge
    c = c' + beta
    return (Unblinder(alpha, R'.x), BlindedMessage(c))
}

fn blindSign(signer: privkey, nonce: privkey, blindedMessage: BlindedMessage): BlindedSignature {
    c = blindedMessage.challenge
    x = signer
    k = nonce

    s = k + c*x
    return BlindedSignature(s)
}

fn unblind(unblinder: Unblinder, blindedSig: BlindedSignature): Signature {
    Rx = unblinder.Rx
    s = blindedSig.s + unblinder.alpha
    return Signature(Rx, s)
} 

// implemented according to sipa's spec
fn verify(pubkey: Point, message: bytes, sig: Signature): bool {
    pk = pubkey
    m = message
    P = pubkey

    (r, s) = sig
    e = int(hash(r || P || m)) % curve.n
    R = s*G - e*P

    return false if isInfinitePoint(R)
    return false if jacobi(R.y) !== 1 // <-- Fails 50% here
    return false if R.x !== r
    return true
}

Aquí está la prueba que fallará el 50% de las veces:

noncePriv = rand()
signerPriv = rand()
noncePub = noncePriv * curve.G
signerPub = signerPriv * curve.G
message = hash('my message')

// blind
(unblinder, blindedMessage) = blindMessage(noncePub, signerPub, message)

// sign
blindedSig = blindSign(signerPriv, noncePriv, blindedMessage)

// unblind 
sig = unblind(unblinder, blindedSig)

// verify
verified = verify(signerPub, message, sig)

assert(verified)

sign()Los usos de la especificación nonce = jacobi(y) === 1 ? nonce : curve.N - noncepara forzar un residuo cuadrático y, y asumiría que mi prueba falla porque el 50% del tiempo genera un no residuo y. Sin embargo, no pude averiguar dónde y cómo aplicar esto en mi código relacionado con ciegos. Ninguno de mis esfuerzos tuvo un efecto en mi tasa de aprobación del 50%.

Solo para el beneficio de personas aleatorias que llegan a esta pregunta: una firma ciega construida de esta manera es insegura si el firmante ciego está dispuesto a hacer varias firmas ciegas al mismo tiempo. La firma ciega debe protegerse contra un ataque de una firma más basado en el algoritmo de Wagner.

Respuestas (1)

Andrew Poelstra sugiere que:

R' = R + alpha*G + beta*G

debe ponerse en un bucle .. siempre que Rx no sea un residuo cuadrático. Puede cambiar la versión beta y esto debería darle compatibilidad con el esquema de sipa sin seguridad, pérdida de privacidad o pérdida de capacidad de vinculación ciega.