//*********************************************************
//
// Copyright (c) Microsoft. All rights reserved.
// This code is licensed under the Apache License Version 2.0.
// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************
package com.microsoft.uprove;
import java.io.IOException;
import java.math.BigInteger;
import com.microsoft.uprove.FieldZq.ZqElement;
/**
* Provides protocol helper functions.
*/
final class ProtocolHelper {
/**
* Private constructor to prevent instantiation.
*/
private ProtocolHelper() {
super();
}
/**
* Computes the product of a series of exponentiations.
* @param bases an array of base elements.
* @param exponents an array of exponents, each of which may be
* <code>null</code> (treated as if it were <code>1</code>).
* @return the desired product.
*/
static GroupElement computeProduct(final GroupElement[] bases,
final ZqElement[] exponents) {
final GroupElement retVal = bases[0].getGroup().getIdentity();
for (int i = 0; i < exponents.length; ++i) {
retVal.multiplyAssign(
exponents[i] != null
? bases[i].exponentiate(exponents[i])
: bases[i]);
}
return retVal;
}
/**
* Computes the protocol value xt.
* @param ip the issuer parameters.
* @param tokenInformation the token information field value.
* @return the xt element.
*/
static ZqElement computeXt(IssuerParametersInternal ip, byte[] tokenInformation) {
HashFunction H = ip.getHashFunction();
// 0x01
H.update((byte) 1);
// P
H.update(ip.getIssuerParametersDigest());
// TI
H.update(tokenInformation);
return H.getZqDigest();
}
/**
* Computes the protocol value xi.
* @param ip the issuer parameters.
* @param index the attribute index, one-based.
* @param A the attribute value A_index.
* @return the x_index element.
* @throws IOException
*/
static ZqElement computeXi(IssuerParametersInternal ip, int index, byte[] A) throws IOException {
byte ei = ip.getEncodingBytes()[index-1];
if (ei == (byte)1) {
if (A == null || A.length == 0) {
return ip.getGroup().getZq().getZero();
} else {
HashFunction H = ip.getHashFunction();
H.update(A);
return H.getZqDigest();
}
} else if (ei == (byte)0) {
if (A == null) {
throw new NullPointerException("Array A can't be null when e_index == 0");
}
return ip.getGroup().getZq().getPositiveElement(A);
} else {
throw new IllegalArgumentException("unsupported encoding byte value: " + ei);
}
}
/**
* Computes the token ID
* @param ip
* @return
*/
static byte[] computeTokenID(IssuerParametersInternal ip, UProveTokenInternal upti) {
HashFunction H = ip.getHashFunction();
H.update(upti.getPublicKey());
H.update(upti.getSigmaZ());
H.update(upti.getSigmaC());
H.update(upti.getSigmaR());
return H.getByteDigest();
}
static boolean isTokenSignatureValid(IssuerParametersInternal ip, UProveTokenInternal upti) throws IOException {
PrimeOrderGroup Gq = ip.getGroup();
// check that h != 1
if (Gq.getIdentity().equals(upti.getPublicKey())) {
return false;
}
// check sigma_c'
HashFunction H = ip.getHashFunction();
H.update(upti.getPublicKey());
H.update(upti.getProverInformation());
H.update(upti.getSigmaZ());
GroupElement[] base;
ZqElement[] exponents;
base = new GroupElement[] {Gq.getGenerator(), ip.getPublicKey()[0]};
exponents = new ZqElement[] {upti.getSigmaR(), upti.getSigmaC().negate()};
H.update(computeProduct(base, exponents));
base = new GroupElement[] {upti.getPublicKey(), upti.getSigmaZ()};
exponents = new ZqElement[] {upti.getSigmaR(), upti.getSigmaC().negate()};
H.update(computeProduct(base, exponents));
if (!H.getZqDigest().equals(upti.getSigmaC())){
return false;
}
// signature is valid
return true;
}
/**
* Computes the x array from an array of attributes. The returned array contains:
* x[0] = 1, x[1] = a[0], ..., x[n] = a[-1n], x[n+1] = xt
* @param ip the issuer parameters
* @param attributes the attributes array
* @param tokenInformation the token information field
* @return the x array
* @throws IOException
*/
static ZqElement[] computeXArray(IssuerParametersInternal ip, byte[][] attributes, byte[] tokenInformation) throws IOException {
// the attribute indices run from 1 to n
int[] attributeIndices = new int[attributes.length];
for (int i=0; i<attributes.length; i++) {
attributeIndices[i] = i+1;
}
return computeXArray(ip, attributeIndices, attributes, tokenInformation);
}
/**
* Computes the x array from an array of attributes. The returned array contains:
* x[0] = 1, x[1] = a[0], ..., x[n] = a[-1n], x[n+1] = xt
* @param ip the issuer parameters
* @param attributeIndices the indices of the attributes
* @param attributes the attributes array
* @param tokenInformation the token information field
* @return the x array
* @throws IOException
*/
static ZqElement[] computeXArray(IssuerParametersInternal ip, int[] attributeIndices, byte[][] attributes, byte[] tokenInformation) throws IOException {
if (attributeIndices.length != attributes.length) {
throw new IllegalArgumentException("attributeIndices and attributes array must have the same length");
}
int n = attributes.length + 2;
// compute the x_i
ZqElement[] x = new ZqElement[n];
x[0] = ip.getGroup().getZq().getOne();
for (int index=1; index<n-1; index++) {
x[index] = computeXi(ip, attributeIndices[index-1], attributes[index-1]);
}
x[n-1] = computeXt(ip, tokenInformation);
return x;
}
static class GenerateChallengeOutput {
private ZqElement c;
private byte[] mdPrime;
GenerateChallengeOutput(ZqElement c, byte[] mdPrime) {
this.c = c;
this.mdPrime = mdPrime;
}
ZqElement getC() {
return c;
}
byte[] getMdPrime() {
return mdPrime;
}
}
static GenerateChallengeOutput genChallenge(IssuerParametersInternal ip, UProveTokenInternal upti, byte[] a, byte[] m, byte[] md, int[] disclosed, ZqElement[] disclosedX) {
byte[] UIDt = computeTokenID(ip, upti);
int n = ip.getEncodingBytes().length;
ZqElement[] f = new ZqElement[n]; // null
for (int i=0; i<disclosed.length ; i++) {
int index = disclosed[i];
f[index-1] = disclosedX[i];
}
HashFunction H = ip.getHashFunction();
H.update(disclosed.length);
for (int i=0; i<disclosed.length; i++) {
H.update(disclosed[i]);
}
H.update(f.length);
for (int i=0; i<f.length; i++) {
if (f[i] == null) {
H.updateNull();
} else {
H.update(f[i]);
}
}
byte[] F = H.getByteDigest();
H.reset();
byte[] mdPrime = null;
ZqElement c = null;
if (upti.isDeviceProtected()) {
H.update(UIDt);
H.update(a);
H.update(m);
H.update(F);
mdPrime = H.getByteDigest();
H.reset();
H.update(md);
H.update(mdPrime);
c = H.getZqDigest();
} else {
H.update(UIDt);
H.update(a);
H.update(m);
H.update(F);
c = H.getZqDigest();
}
return new GenerateChallengeOutput(c, mdPrime);
}
static ZqElement genChallenge(IssuerParametersInternal ipi,
byte[] md, byte[] mdPrime) {
HashFunction H = ipi.getHashFunction();
H.update(md);
H.update(mdPrime);
return H.getZqDigest();
}
/**
* Returns the list of undisclosed attribute indices from the list of disclosed ones.
* E.g., for numOfAttribs = 5 and disclosed = {1,3,5}, the returned value is {2,4}.
* @param numOfAttribs total number of attributes
* @param disclosed the list of disclosed attribute indices.
* @return list of undisclosed attribute indices.
*/
static int[] getUndisclosedIndices(int numOfAttribs, int[] disclosed) {
if (numOfAttribs < 0) {
throw new IllegalArgumentException("numOfAttribs must be greater or equal to 0.");
}
if (disclosed.length > numOfAttribs) {
throw new IllegalArgumentException("invalid size for disclosed.");
}
int[] undisclosed = new int[numOfAttribs - disclosed.length];
int d_index = 0;
int u_index = 0;
for (int i=1; i<=numOfAttribs; i++) {
if (d_index < disclosed.length && // we don't want to run over
disclosed[d_index] == i) {
d_index++;
} else {
undisclosed[u_index++] = i;
}
}
return undisclosed;
}
static ZqElement[] getZqElementArray(FieldZq Zq, byte[][] encoded) throws IOException {
ZqElement[] elements = new ZqElement[encoded.length];
for (int i=0; i<encoded.length; i++) {
elements[i] = Zq.getPositiveElement(encoded[i]);
}
return elements;
}
static GroupElement[] getGroupElementArray(PrimeOrderGroup Gq, byte[][] encoded) throws IOException {
return Gq.getElementArray(encoded);
}
static byte[][] getEncodedArray(Element[] elements) {
byte[][] encoded = new byte[elements.length][];
for (int i=0; i<elements.length; i++) {
encoded[i] = elements[i].toByteArray();
}
return encoded;
}
/**
*/
static public byte[] getMagnitude(BigInteger i) {
if (i.signum() == 0) {
// return a zero byte rather than an empty byte array
return new byte[] { 0 };
}
// all non-zero elements are positive
assert 1 == i.signum();
// how many non-sign bits are there?
final int bitLen = i.bitLength();
assert bitLen > 0 : bitLen;
// how many bytes should that take up?
final int byteLen = (bitLen + 7) / 8;
// get the two's complement representation
final byte[] twosComp = i.toByteArray();
// is there an extra leading byte for the sign bit?
if (twosComp.length != byteLen) {
// must be at least two bytes.
assert twosComp.length > 1;
// the most significant byte must be only the sign bit.
assert 0 == twosComp[0] : twosComp[0];
// the most significant bit of the next byte must be opposite
assert 0 != (twosComp[1] & 0x80) : (twosComp[1] & 0x80);
// strip off the extra byte holding the sign bit
final byte[] retVal = new byte[twosComp.length - 1];
System.arraycopy(twosComp, 1, retVal, 0, retVal.length);
return retVal;
}
// else the most significant byte isn't only there for the sign bit
return twosComp;
}
}