Skip to content

Commit

Permalink
Add blind RSA protocol support (#308)
Browse files Browse the repository at this point in the history
This change adds a generic interface for two-message blind signature
protocols, along with a concrete implementation of the blind RSA
protocol as currently being specified by the CFRG. This protocol is
a very simple wrapper around the existing RSA implementation in the
Go standard library.
  • Loading branch information
chris-wood authored Dec 20, 2021
1 parent cd788e3 commit 8147cc8
Show file tree
Hide file tree
Showing 6 changed files with 1,164 additions and 0 deletions.
215 changes: 215 additions & 0 deletions blindsign/blindrsa/blindrsa.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
package blindrsa

// This package implements the blind RSA protocol based on the CFRG specification:
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-rsa-blind-signatures-02

import (
"crypto/rand"
"crypto/rsa"
"crypto/subtle"
"errors"
"hash"
"io"
"math/big"

"github.com/cloudflare/circl/blindsign"
)

// An RSAVerifier represents a Verifier in the RSA blind signature protocol.
// It carries state needed to produce and validate an RSA blind signature.
type RSAVerifier struct {
// Public key of the Signer
pk *rsa.PublicKey

// Hash function used in producing the message signature
hash hash.Hash
}

// NewRSAVerifier creates a new RSAVerifier using the corresponding Signer parameters.
func NewRSAVerifier(pk *rsa.PublicKey, hash hash.Hash) RSAVerifier {
return RSAVerifier{
pk: pk,
hash: hash,
}
}

func encodeMessageEMSAPSS(message []byte, key *rsa.PublicKey, hash hash.Hash, salt []byte) ([]byte, error) {
hash.Reset() // Ensure the hash state is cleared
hash.Write(message)
digest := hash.Sum(nil)
hash.Reset()
emBits := key.N.BitLen() - 1
encodedMsg, err := emsaPSSEncode(digest[:], emBits, salt, hash)
return encodedMsg, err
}

func generateBlindingFactor(random io.Reader, key *rsa.PublicKey) (*big.Int, *big.Int, error) {
randReader := random
if randReader == nil {
randReader = rand.Reader
}
r, err := rand.Int(randReader, key.N)
if err != nil {
return nil, nil, err
}

if r.Sign() == 0 {
r = bigOne
}
rInv := new(big.Int).ModInverse(r, key.N)
if rInv == nil {
return nil, nil, ErrInvalidBlind
}

return r, rInv, nil
}

func (v RSAVerifier) fixedBlind(message, salt []byte, r, rInv *big.Int) ([]byte, blindsign.VerifierState, error) {
encodedMsg, err := encodeMessageEMSAPSS(message, v.pk, v.hash, salt)
if err != nil {
return nil, nil, err
}

m := new(big.Int).SetBytes(encodedMsg)

bigE := big.NewInt(int64(v.pk.E))
x := new(big.Int).Exp(r, bigE, v.pk.N)
z := new(big.Int).Set(m)
z.Mul(z, x)
z.Mod(z, v.pk.N)

blindedMsg := z.Bytes()

return blindedMsg, RSAVerifierState{
encodedMsg: encodedMsg,
verifier: v,
rInv: rInv,
}, nil
}

// Blind initializes the blind RSA protocol using an input message and source of randomness. The
// signature includes a randomly generated PSS salt whose length equals the size of the underlying
// hash function. This function fails if randomness was not provided.
//
// See the specification for more details:
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-rsa-blind-signatures-02#section-5.1.1
func (v RSAVerifier) Blind(random io.Reader, message []byte) ([]byte, blindsign.VerifierState, error) {
if random == nil {
return nil, nil, ErrInvalidRandomness
}

salt := make([]byte, v.hash.Size())
_, err := random.Read(salt)
if err != nil {
return nil, nil, err
}

r, rInv, err := generateBlindingFactor(random, v.pk)
if err != nil {
return nil, nil, err
}

return v.fixedBlind(message, salt, r, rInv)
}

// An RSAVerifierState carries state needed to complete the blind signature protocol
// as a verifier.
type RSAVerifierState struct {
// An RSA verifier carrying Signer verification state
verifier RSAVerifier

// The hashed and encoded message being signed
encodedMsg []byte

// Inverse of the blinding factor produced by the Verifier
rInv *big.Int
}

func verifyBlindSignature(pub *rsa.PublicKey, hashed, sig []byte) error {
m := new(big.Int).SetBytes(hashed)
bigSig := new(big.Int).SetBytes(sig)

c := encrypt(new(big.Int), pub, bigSig)
if subtle.ConstantTimeCompare(m.Bytes(), c.Bytes()) == 1 {
return nil
} else {
return rsa.ErrVerification
}
}

// Finalize computes and outputs the final signature, if it's valid. Otherwise, it returns an error.
//
// See the specification for more details:
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-rsa-blind-signatures-02#section-5.1.3
func (state RSAVerifierState) Finalize(data []byte) ([]byte, error) {
kLen := (state.verifier.pk.N.BitLen() + 7) / 8
if len(data) != kLen {
return nil, ErrUnexpectedSize
}

z := new(big.Int).SetBytes(data)
s := new(big.Int).Set(state.rInv)
s.Mul(s, z)
s.Mod(s, state.verifier.pk.N)

sig := s.Bytes()

err := verifyBlindSignature(state.verifier.pk, state.encodedMsg, sig)
if err != nil {
return nil, err
}

return sig, nil
}

// An RSASigner represents the Signer in the blind RSA protocol.
// It carries the raw RSA private key used for signing blinded messages.
type RSASigner struct {
// An RSA private key
sk *rsa.PrivateKey
}

// NewRSASigner creates a new Signer for the blind RSA protocol using an RSA private key.
func NewRSASigner(sk *rsa.PrivateKey) RSASigner {
return RSASigner{
sk: sk,
}
}

// BlindSign blindly computes the RSA operation using the Signer's private key on the blinded
// message input, if it's of valid length, and returns an error should the function fail.
//
// See the specification for more details:
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-rsa-blind-signatures-02#section-5.1.2
func (signer RSASigner) BlindSign(data []byte) ([]byte, error) {
kLen := (signer.sk.N.BitLen() + 7) / 8
if len(data) != kLen {
return nil, ErrUnexpectedSize
}

m := new(big.Int).SetBytes(data)
if m.Cmp(signer.sk.N) > 0 {
return nil, ErrInvalidMessageLength
}

s, err := decryptAndCheck(rand.Reader, signer.sk, m)
if err != nil {
return nil, err
}

return s.Bytes(), nil
}

var (
// ErrUnexpectedSize is the error used if the size of a parameter does not match its expected value.
ErrUnexpectedSize = errors.New("blindsign/blindrsa: unexpected input size")

// ErrInvalidMessageLength is the error used if the size of a protocol message does not match its expected value.
ErrInvalidMessageLength = errors.New("blindsign/blindrsa: invalid message length")

// ErrInvalidBlind is the error used if the blind generated by the Verifier fails.
ErrInvalidBlind = errors.New("blindsign/blindrsa: invalid blind")

// ErrInvalidRandomness is the error used if caller did not provide randomness to the Blind() function.
ErrInvalidRandomness = errors.New("blindsign/blindrsa: invalid random parameter")
)
Loading

0 comments on commit 8147cc8

Please sign in to comment.