Skip to content

Instantly share code, notes, and snippets.

@mjethani
Last active June 22, 2019 13:04
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save mjethani/e6d8b3e458ff59ef5b6e to your computer and use it in GitHub Desktop.
Save mjethani/e6d8b3e458ff59ef5b6e to your computer and use it in GitHub Desktop.
An example showing Chaum's RSA blind signature scheme (aka "anonymous digital cash") in Java using the Bouncy Castle crypto library
/* ----------------------------------------------------------------------------
* Author: Manish Jethani (manish.jethani@gmail.com)
* Date: July 21, 2014
*
* This is an example showing Chaum's RSA blind signature scheme using the
* Bouncy Castle crypto library.
*
* To compile and run this, you'll need the Java SDK and the Java version of
* the Bouncy Castle APIs.
*
* http://www.java.com/
* http://www.bouncycastle.org/
*
* If you see any errors/mistakes, please send me email at
* manish.jethani@gmail.com.
*
* http://manishjethani.com/
* ------------------------------------------------------------------------- */
import java.math.BigInteger;
import java.security.SecureRandom;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.engines.RSABlindingEngine;
import org.bouncycastle.crypto.engines.RSAEngine;
import org.bouncycastle.crypto.generators.RSABlindingFactorGenerator;
import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;
import org.bouncycastle.crypto.params.RSABlindingParameters;
import org.bouncycastle.crypto.params.RSAKeyGenerationParameters;
import org.bouncycastle.crypto.params.RSAKeyParameters;
import org.bouncycastle.crypto.signers.PSSSigner;
import org.bouncycastle.util.encoders.Base64;
/* ------------------------------------
* Data structures
* --------------------------------- */
// These are the objects that are supposed to go over the wire between Alice
// and her bank.
interface IBank {
// The bank's RSA public key
RSAKeyParameters getPublic();
// Sign a coin request
byte[] sign(ICoinRequest coinRequest);
// Verify a coin
boolean verify(ICoin coin);
}
interface ICoin {
// The coin's globally unique ID
byte[] getID();
// The issuing bank's signature on the coin
byte[] getSignature();
}
interface ICoinRequest {
// The message (blind) to be signed by the bank
byte[] getMessage();
}
/* ------------------------------------
* The main class
* --------------------------------- */
public class BlindSignatureExample {
private static IBank bank = createBank();
private static IBank createBank() {
// Create a new bank using a freshly generated RSA key pair.
return new Bank(Util.generateKeyPair());
}
public static void main(String[] args) throws CryptoException {
// Create a "protocoin" using the bank's public key. The protocoin
// contains an internal blinding factor that is used to blind the
// message to be signed by the bank.
Protocoin protocoin = new Protocoin(bank.getPublic());
// Generate a coin request.
CoinRequest coinRequest = protocoin.generateCoinRequest();
printCoinRequest(coinRequest);
// Ask the bank to sign the coin request.
// Note: In practice the bank will be on a remote server and this will
// be an asynchronous operation. The bank will verify Alice's
// credentials and debit her account for every coin it issues.
// Needless to say, the connection to the bank would have to be over a
// secure channel.
byte[] signature = bank.sign(coinRequest);
printBankSignature(signature);
// Create a new coin using the bank's signature.
Coin coin = protocoin.createCoin(signature);
printCoin(coin);
// The signature on the coin is different from the one the bank
// returned earlier (magic!). Will the bank accept the coin as valid?
// Let's see ...
boolean valid = bank.verify(coin);
assert valid : "Impossible! Bank rejects its own coin!";
if (valid) {
// It should always print "OK"
System.out.println("OK");
} else {
System.out.println("Fail!");
}
}
private static void printCoinRequest(CoinRequest coinRequest) {
System.out.println("MESSAGE TO BE SIGNED BY THE BANK:");
System.out.println("");
System.out.println(Base64.toBase64String(coinRequest.getMessage()));
System.out.println("");
}
private static void printBankSignature(byte[] signature) {
System.out.println("THE BANK'S SIGNATURE:");
System.out.println("");
System.out.println(Base64.toBase64String(signature));
System.out.println("");
}
private static void printCoin(Coin coin) {
System.out.println("COIN:");
System.out.println("");
System.out.println(Base64.toBase64String(coin.getID()));
System.out.println("");
System.out.println(Base64.toBase64String(coin.getSignature()));
System.out.println("");
}
}
/* ------------------------------------
* Implementation
* --------------------------------- */
class Bank implements IBank {
private final AsymmetricCipherKeyPair keys;
public Bank(AsymmetricCipherKeyPair keys) {
this.keys = keys;
}
public RSAKeyParameters getPublic() {
return (RSAKeyParameters) keys.getPublic();
}
public byte[] sign(ICoinRequest coinRequest) {
// Sign the coin request using our private key.
byte[] message = coinRequest.getMessage();
RSAEngine engine = new RSAEngine();
engine.init(true, keys.getPrivate());
return engine.processBlock(message, 0, message.length);
}
public boolean verify(ICoin coin) {
// Verify that the coin has a valid signature using our public key.
byte[] id = coin.getID();
byte[] signature = coin.getSignature();
PSSSigner signer = new PSSSigner(new RSAEngine(), new SHA1Digest(), 20);
signer.init(false, keys.getPublic());
signer.update(id, 0, id.length);
return signer.verifySignature(signature);
}
}
class Coin implements ICoin {
private final byte[] id;
private final byte[] signature;
public Coin(byte[] id, byte[] signature) {
this.id = id;
this.signature = signature;
}
public byte[] getID() {
return id;
}
public byte[] getSignature() {
return signature;
}
}
class CoinRequest implements ICoinRequest {
private final byte[] message;
public CoinRequest(byte[] message) {
this.message = message;
}
public byte[] getMessage() {
return message;
}
}
class Protocoin {
private final byte[] coinID;
private final RSABlindingParameters blindingParams;
public Protocoin(RSAKeyParameters pub) {
// Create a 128-bit globally unique ID for the coin.
coinID = Util.getRandomBytes(16);
// Generate a blinding factor using the bank's public key.
RSABlindingFactorGenerator blindingFactorGenerator
= new RSABlindingFactorGenerator();
blindingFactorGenerator.init(pub);
BigInteger blindingFactor
= blindingFactorGenerator.generateBlindingFactor();
blindingParams = new RSABlindingParameters(pub, blindingFactor);
}
public CoinRequest generateCoinRequest() throws CryptoException {
// "Blind" the coin and generate a coin request to be signed by the
// bank.
PSSSigner signer = new PSSSigner(new RSABlindingEngine(),
new SHA1Digest(), 20);
signer.init(true, blindingParams);
signer.update(coinID, 0, coinID.length);
byte[] sig = signer.generateSignature();
return new CoinRequest(sig);
}
public Coin createCoin(byte[] signature) {
// "Unblind" the bank's signature (so to speak) and create a new coin
// using the ID and the unblinded signature.
RSABlindingEngine blindingEngine = new RSABlindingEngine();
blindingEngine.init(false, blindingParams);
byte[] s = blindingEngine.processBlock(signature, 0, signature.length);
return new Coin(coinID, s);
}
}
class Util {
public static byte[] getRandomBytes(int count) {
byte[] bytes = new byte[count];
new SecureRandom().nextBytes(bytes);
return bytes;
}
public static AsymmetricCipherKeyPair generateKeyPair() {
// Generate a 2048-bit RSA key pair.
RSAKeyPairGenerator generator = new RSAKeyPairGenerator();
generator.init(new RSAKeyGenerationParameters(
new BigInteger("10001", 16), new SecureRandom(), 2048,
80));
return generator.generateKeyPair();
}
}
@nopara73
Copy link

I quickly translated it to .NET as an exercise. I thought I drop it here.

using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Signers;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities.Encoders;
using System;

namespace ChaumianBlinding
{
    interface IBank
    {
        // The bank's RSA public key
        RsaKeyParameters GetPublic();

        // Sign a coin request
        byte[] Sign(ICoinRequest coinRequest);

        // Verify a coin
        bool Verify(ICoin coin);
    }

    interface ICoin
    {
        // The coin's globally unique ID
        byte[] GetID();

        // The issuing bank's signature on the coin
        byte[] GetSignature();
    }

    interface ICoinRequest
    {
        // The message (blind) to be signed by the bank
        byte[] GetMessage();
    }

    class Program
    {
        private static IBank bank = CreateBank();

        private static IBank CreateBank()
        {
            // Create a new bank using a freshly generated RSA key pair.
            return new Bank(Util.GenerateKeyPair());
        }

        static void Main(string[] args)
        {
            // Create a "protocoin" using the bank's public key. The protocoin
            // contains an internal blinding factor that is used to blind the
            // message to be signed by the bank.
            Protocoin protocoin = new Protocoin(bank.GetPublic());

            // Generate a coin request.
            CoinRequest coinRequest = protocoin.GenerateCoinRequest();

            PrintCoinRequest(coinRequest);

            // Ask the bank to sign the coin request.

            // Note: In practice the bank will be on a remote server and this will
            // be an asynchronous operation. The bank will verify Alice's
            // credentials and debit her account for every coin it issues.
            // Needless to say, the connection to the bank would have to be over a
            // secure channel.

            byte[] signature = bank.Sign(coinRequest);

            PrintBankSignature(signature);

            // Create a new coin using the bank's signature.
            Coin coin = protocoin.CreateCoin(signature);

            PrintCoin(coin);

            // The signature on the coin is different from the one the bank
            // returned earlier (magic!). Will the bank accept the coin as valid?
            // Let's see ...
            bool valid = bank.Verify(coin);

            if (valid)
            {
                // It should always print "OK"
                Console.WriteLine("OK");
            }
            else
            {
                Console.WriteLine("Fail!");
            }

            Console.ReadKey();
        }

        private static void PrintCoinRequest(CoinRequest coinRequest)
        {
            Console.WriteLine("MESSAGE TO BE SIGNED BY THE BANK:");
            Console.WriteLine("");
            Console.WriteLine(Base64.ToBase64String(coinRequest.GetMessage()));
            Console.WriteLine("");
        }

        private static void PrintBankSignature(byte[] signature)
        {
            Console.WriteLine("THE BANK'S SIGNATURE:");
            Console.WriteLine("");
            Console.WriteLine(Base64.ToBase64String(signature));
            Console.WriteLine("");
        }

        private static void PrintCoin(Coin coin)
        {
            Console.WriteLine("COIN:");
            Console.WriteLine("");
            Console.WriteLine(Base64.ToBase64String(coin.GetID()));
            Console.WriteLine("");
            Console.WriteLine(Base64.ToBase64String(coin.GetSignature()));
            Console.WriteLine("");
        }
    }

    class Bank : IBank
    {

        private AsymmetricCipherKeyPair keys;

        public Bank(AsymmetricCipherKeyPair keys)
        {
            this.keys = keys;
        }

        public RsaKeyParameters GetPublic()
        {
            return (RsaKeyParameters)keys.Public;
        }

        public byte[] Sign(ICoinRequest coinRequest)
        {
            // Sign the coin request using our private key.
            byte[] message = coinRequest.GetMessage();

            RsaEngine engine = new RsaEngine();
            engine.Init(true, keys.Private);

            return engine.ProcessBlock(message, 0, message.Length);
        }

        public bool Verify(ICoin coin)
        {
            // Verify that the coin has a valid signature using our public key.
            byte[] id = coin.GetID();
            byte[] signature = coin.GetSignature();

            PssSigner signer = new PssSigner(new RsaEngine(), new Sha1Digest(), 20);
            signer.Init(false, keys.Public);

            signer.BlockUpdate(id, 0, id.Length);

            return signer.VerifySignature(signature);
        }
    }

    class Coin : ICoin
    {

        private byte[] id;
        private byte[] signature;

        public Coin(byte[] id, byte[] signature)
        {
            this.id = id;
            this.signature = signature;
        }

        public byte[] GetID()
        {
            return id;
        }

        public byte[] GetSignature()
        {
            return signature;
        }
    }

    class CoinRequest : ICoinRequest
    {

        private byte[] message;

        public CoinRequest(byte[] message)
        {
            this.message = message;
        }

        public byte[] GetMessage()
        {
            return message;
        }
    }

    class Protocoin
    {
        private byte[] coinID;
        private RsaBlindingParameters blindingParams;

        public Protocoin(RsaKeyParameters pub)
        {
            // Create a 128-bit globally unique ID for the coin.
            coinID = Util.GetRandomBytes(16);

            // Generate a blinding factor using the bank's public key.
            RsaBlindingFactorGenerator blindingFactorGenerator
                = new RsaBlindingFactorGenerator();
            blindingFactorGenerator.Init(pub);

            BigInteger blindingFactor
                = blindingFactorGenerator.GenerateBlindingFactor();

            blindingParams = new RsaBlindingParameters(pub, blindingFactor);
        }

        public CoinRequest GenerateCoinRequest()
        {
            // "Blind" the coin and generate a coin request to be signed by the
            // bank.
            PssSigner signer = new PssSigner(new RsaBlindingEngine(),
                    new Sha1Digest(), 20);
            signer.Init(true, blindingParams);

            signer.BlockUpdate(coinID, 0, coinID.Length);

            byte[] sig = signer.GenerateSignature();

            return new CoinRequest(sig);
        }

        public Coin CreateCoin(byte[] signature)
        {
            // "Unblind" the bank's signature (so to speak) and create a new coin
            // using the ID and the unblinded signature.
            RsaBlindingEngine blindingEngine = new RsaBlindingEngine();
            blindingEngine.Init(false, blindingParams);

            byte[] s = blindingEngine.ProcessBlock(signature, 0, signature.Length);

            return new Coin(coinID, s);
        }
    }

    class Util
    {
        public static byte[] GetRandomBytes(int count)
        {
            byte[] bytes = new byte[count];
            new SecureRandom().NextBytes(bytes);
            return bytes;
        }

        public static AsymmetricCipherKeyPair GenerateKeyPair()
        {
            // Generate a 2048-bit RSA key pair.
            RsaKeyPairGenerator generator = new RsaKeyPairGenerator();
            generator.Init(new RsaKeyGenerationParameters(
                        new BigInteger("10001", 16), 
                        new SecureRandom(), 
                        2048,
                        80));
            return generator.GenerateKeyPair();
        }
    }
}

@nopara73
Copy link

nopara73 commented Nov 7, 2017

If someone would like to use this example, please take a look at this review: zkSNACKs/WalletWasabi#76 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment