Blind Signature

From Crypto++ Wiki
Jump to navigation Jump to search

A Blind Signature is a signature over a blinded or masked message. Blind signatures are typically used in privacy-related protocols where the signer of the message is not necessarily the author of the message. For example, blind signatures can be used in cryptocurrency for sender anonymity. Also see A New Blind ECDSA Scheme for Bitcoin Transaction Anonymity.

The Crypto++ library does not offer blind signature classes. As far as we know there is no standard covering the signature scheme. The lack of standardization will surely cause interop problems. For example, the code below uses SHA256 to hash the message to be signed, while RSA Blind Signature Scheme for golang uses full domain hashing. Also see Is there a standard padding/format for RSA Blind Signatures? on Crypto.SE.

Blind signature can achieved with the following code. Thanks to Cory Geesaman for raising the topic on the mailing list. Also see Is there a standard padding/format for RSA Blind Signatures?, Usability of padding scheme in blinded RSA signature? and RSA blind signatures in practice on the Crypto Stack Exchange.

The code below is based on information for Raw RSA. The article explains the use of functions like ApplyFunction and CalculateInverse. Also see ‎RSA Signature Schemes for other ways to sign a message using RSA.

#include "cryptlib.h"
#include "integer.h"
#include "nbtheory.h"
#include "osrng.h"
#include "rsa.h"
#include "sha.h"

#include <iostream>
#include <stdexcept>

int main(int argc, char* argv[])
    using namespace CryptoPP;

    // Bob artificially small key pair
    AutoSeededRandomPool prng;
    RSA::PrivateKey privKey;

    privKey.GenerateRandomWithKeySize(prng, 64);
    RSA::PublicKey pubKey(privKey);

    // Convenience
    const Integer& n = pubKey.GetModulus();
    const Integer& e = pubKey.GetPublicExponent();
    const Integer& d = privKey.GetPrivateExponent();

    // Print params
    std::cout << "Pub mod: " << std::hex << pubKey.GetModulus() << std::endl;
    std::cout << "Pub exp: " << std::hex << e << std::endl;
    std::cout << "Priv mod: " << std::hex << privKey.GetModulus() << std::endl;
    std::cout << "Priv exp: " << std::hex << d << std::endl;

    // For sizing the hashed message buffer. This should be SHA256 size.
    const size_t SIG_SIZE = UnsignedMin(SHA256::BLOCKSIZE, n.ByteCount());

    // Scratch
    SecByteBlock buff1, buff2, buff3;

    // Alice original message to be signed by Bob
    SecByteBlock orig((const byte*)"secret", 6);
    Integer m(, orig.size());
    std::cout << "Message: " << std::hex << m << std::endl;

    // Hash message per Rabin (1979)
    SHA256 hash1;
    hash1.CalculateTruncatedDigest(buff1, buff1.size(), orig, orig.size());

    // H(m) as Integer
    Integer hm(, buff1.size());
    std::cout << "H(m): " << std::hex << hm << std::endl;

    // Alice blinding
    Integer r;
    do {
        r.Randomize(prng, Integer::One(), n - Integer::One());
    } while (!RelativelyPrime(r, n));

    // Blinding factor
    Integer b = a_exp_b_mod_c(r, e, n);
    std::cout << "Random: " << std::hex << b << std::endl;

    // Alice blinded message
    Integer mm = a_times_b_mod_c(hm, b, n);
    std::cout << "Blind msg: " << std::hex << mm << std::endl;

    // Bob sign
    Integer ss = privKey.CalculateInverse(prng, mm);
    std::cout << "Blind sign: " << ss << std::endl;

    // Alice checks s(s'(x)) = x. This is from Chaum's paper
    Integer ck = pubKey.ApplyFunction(ss);
    std::cout << "Check sign: " << ck << std::endl;
    if (ck != mm)
        throw std::runtime_error("Alice cross-check failed");

    // Alice remove blinding
    Integer s = a_times_b_mod_c(ss, r.InverseMod(n), n);
    std::cout << "Unblind sign: " << std::hex << s << std::endl;

    // Eve verifies
    Integer v = pubKey.ApplyFunction(s);    
    std::cout << "Verify: " << std::hex << v << std::endl;

    // Convert to a string
    size_t req = v.MinEncodedSize();
    v.Encode(&buff2[0], buff2.size());

    // Hash message per Rabin (1979)
    SHA256 hash2;
    hash2.CalculateTruncatedDigest(buff3, buff3.size(), orig, orig.size());

    // Constant time compare
    bool equal = buff2.size() == buff3.size() && VerifyBufsEqual(,, buff3.size());

    if (!equal)
        throw std::runtime_error("Eve verified failed");

    std::cout << "Verified signature" << std::endl;

    return 0;

Running the program results in:

$ g++ -g2 -O3 -I. blind.cxx ./libcryptopp.a -o blind.exe
$ ./blind.exe
Pub mod: ef0a8e12b020cb43h
Pub exp: 11h
Priv mod: ef0a8e12b020cb43h
Priv exp: 626dc206e636cc49h
Message: 736563726574h
H(m): 2bb80d537b1da3e3h
Random: b5ebee0076e53fcbh
Blind msg: 6b327b608027df1fh
Blind sign: c2e92d5c262a524ch
Check sign: 6b327b608027df1fh
Unblind sign: 58c28011bd85b1f9h
Verify: 2bb80d537b1da3e3h
Verified signature