Skip to content

Instantly share code, notes, and snippets.

@arik-so
Last active June 15, 2018 19:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save arik-so/eaea92d4c7ee0ca28900308716e4ae83 to your computer and use it in GitHub Desktop.
Save arik-so/eaea92d4c7ee0ca28900308716e4ae83 to your computer and use it in GitHub Desktop.
Javascript Ring Signature Experiment
const BigInteger = require('bigi');
const _ = require('lodash');
const prova = require('prova-lib');
const crypto = require('crypto');
const bitcoin = require('bitgo-utxo-lib');
const ecurve = require('ecurve');
let secp256k1 = ecurve.getCurveByName('secp256k1');
/**
* H(x * G)
* @param pubkey
* @returns {*}
*/
const keyHashScalar = (pubkey) => {
const pubkeyBuffer = Buffer.from(pubkey, 'hex');
const pubkeyHash = crypto.createHash('sha256').update(pubkeyBuffer).digest('hex');
return BigInteger.fromHex(pubkeyHash).mod(secp256k1.n);
};
/**
* H(x * G) * G
* @param pubkey
* @param generator
* @returns {*}
*/
const keyHashPoint = (pubkey) => {
const hGenerator = keyHashGenerator(secp256k1.G.getEncoded(true).toString('hex'));
const scalar = keyHashScalar(pubkey);
return hGenerator.multiply(scalar);
};
/**
*
* @param pubkey
* @returns {*}
*/
const keyHashGenerator = (pubkey) => {
const scalar = keyHashScalar(pubkey);
let currentGenerator = secp256k1.pointFromX(false, scalar);
let isOnCurve = secp256k1.isOnCurve(currentGenerator);
let offset = BigInteger.fromHex('01');
while (!isOnCurve) {
// if the point is not on the curve, we increment x by one until it is
currentGenerator = secp256k1.pointFromX(false, scalar.add(offset).mod(secp256k1.n));
isOnCurve = secp256k1.isOnCurve(currentGenerator);
offset = offset.add(offset);
}
return currentGenerator;
};
/**
* returns x * H(x * G) * G
* x is the privkey, xG is the pubkey, H is a hash function
* @param privkey
* @returns {*}
*/
const keyImage = (privkey) => {
const privkeyObject = prova.ECPair.fromPrivateKeyBuffer(Buffer.from(privkey, 'hex'));
const knownPubkey = privkeyObject.getPublicKeyBuffer().toString('hex');
const pubkeyHashPoint = keyHashPoint(knownPubkey);
return pubkeyHashPoint.multiply(privkeyObject.d);
};
const pointToBuffer = (ecPoint) => {
return (new prova.ECPair(null, ecPoint)).getPublicKeyBuffer();
};
const createCommitment = (message, leftValue, rightValue) => {
const messageBuffer = Buffer.from(message, 'utf-8');
const leftBuffer = pointToBuffer(leftValue);
const rightBuffer = pointToBuffer(rightValue);
const fullBuffer = Buffer.concat([messageBuffer, leftBuffer, rightBuffer]);
return crypto.createHash('sha256').update(fullBuffer).digest('hex');
};
const printBufferArray = (buffers) => {
const hexBuffers = buffers.map(b => b.toString('hex'));
console.log(JSON.stringify(hexBuffers, null, 4));
};
const serializeSignature = ([privkeyImage, firstCommitment, ...randomValues]) => {
// convert the point to a public key
const keyImageBuffer = pointToBuffer(privkeyImage);
const commitmentBuffer = Buffer.from(firstCommitment, 'hex');
const randomValueBuffers = randomValues.map(v => v.toBuffer());
const signatureBuffers = [keyImageBuffer, commitmentBuffer, ...randomValueBuffers];
const sigScript = bitcoin.script.compile(signatureBuffers);
return sigScript.toString('hex');
};
const deserializeSignature = (signature) => {
const signatureBuffers = bitcoin.script.decompile(Buffer.from(signature, 'hex'));
printBufferArray(signatureBuffers);
const [privkeyImageBuffer, firstCommitmentBuffer, ...randomValueBuffers] = signatureBuffers;
const privkeyImage = prova.ECPair.fromPublicKeyBuffer(privkeyImageBuffer).__Q;
const firstCommitment = firstCommitmentBuffer.toString('hex');
const randomValues = randomValueBuffers.map(b => BigInteger.fromBuffer(b));
return [privkeyImage, firstCommitment, ...randomValues];
};
const ringSign = (pubkeys, privkey, message) => {
// obtain the index of the public key whose private key we know
const privkeyObject = prova.ECPair.fromPrivateKeyBuffer(Buffer.from(privkey, 'hex'));
const knownPubkey = privkeyObject.getPublicKeyBuffer().toString('hex');
const knownPubkeyIndex = pubkeys.indexOf(knownPubkey);
if (knownPubkeyIndex === -1) {
throw new Error('no supported public keys present');
}
const ringSize = pubkeys.length;
const ringIndex = (index) => index % ringSize;
const privkeyImage = keyImage(privkey);
// initialize commitments and random values
const commitments = _.times(ringSize, _.constant(null));
const randomValues = _.times(ringSize, () => BigInteger.fromHex(crypto.randomBytes(32).toString('hex')).mod(secp256k1.n));
randomValues[knownPubkeyIndex] = null;
// start with the first index
const alpha = BigInteger.fromHex(crypto.randomBytes(32).toString('hex')).mod(secp256k1.n); // some random integer
const generator = secp256k1.G;
const leftValue = generator.multiply(alpha);
const rightValue = keyHashPoint(pubkeys[knownPubkeyIndex]).multiply(alpha);
commitments[ringIndex(knownPubkeyIndex + 1)] = createCommitment(message, leftValue, rightValue);
// fill the remainder of the ring
for (let i = 1; i < ringSize; i++) {
const currentIndex = ringIndex(knownPubkeyIndex + i);
const currentRandomValue = randomValues[currentIndex];
const currentCommitment = commitments[currentIndex];
const currentCommitmentNumber = BigInteger.fromHex(currentCommitment).mod(secp256k1.n);
const currentPubkey = pubkeys[currentIndex];
const currentPubkeyObject = prova.ECPair.fromPublicKeyBuffer(Buffer.from(currentPubkey, 'hex'));
const currentPubkeyPoint = currentPubkeyObject.__Q;
// calculate current L(i) = s(i)*G + c(i) * P(i)
const currentLeftValue = generator.multiply(currentRandomValue).add(currentPubkeyPoint.multiply(currentCommitmentNumber));
// calculate current R(i) = s(i)*H(P(i))*G + c(i) * I
const currentPubkeyHashpoint = keyHashPoint(currentPubkey);
const currentRightValue = currentPubkeyHashpoint.multiply(currentRandomValue).add(privkeyImage.multiply(currentCommitmentNumber));
commitments[ringIndex(currentIndex + 1)] = createCommitment(message, currentLeftValue, currentRightValue);
}
// calculate the necessary random value (s) for the original commitment
// left:
// alpha * G = s * G + c * P <=> alpha = s + c * x <=> s = alpha - c * x
// right:
// alpha * H(P) = s * H(P) + c * I <=> alpha * h(P) = s * h(P) + c * x * h(P) <=> alpha = s + c * x <=> s = alpha - c * x
const referenceCommitment = commitments[knownPubkeyIndex];
const referenceCommitmentNumber = BigInteger.fromHex(referenceCommitment).mod(secp256k1.n);
randomValues[knownPubkeyIndex] = alpha.subtract(referenceCommitmentNumber.multiply(privkeyObject.d).mod(secp256k1.n)).mod(secp256k1.n);
const signature = [privkeyImage, commitments[0], ...randomValues];
return serializeSignature(signature);
};
const verifySignature = (pubkeys, message, signature) => {
const [privkeyImage, firstCommitment, ...randomValues] = deserializeSignature(signature);
const ringSize = randomValues.length;
const ringIndex = (index) => index % ringSize;
const commitments = _.times(ringSize, _.constant(null));
commitments[0] = firstCommitment;
const generator = secp256k1.G;
for (let i = 0; i < ringSize; i++) {
const currentRandomValue = randomValues[i];
const currentCommitment = commitments[i];
const currentCommitmentNumber = BigInteger.fromHex(currentCommitment).mod(secp256k1.n);
const currentPubkey = pubkeys[i];
const currentPubkeyObject = prova.ECPair.fromPublicKeyBuffer(Buffer.from(currentPubkey, 'hex'));
const currentPubkeyPoint = currentPubkeyObject.__Q;
// calculate current L(i) = s(i)*G + c(i) * P(i)
const currentLeftValue = generator.multiply(currentRandomValue).add(currentPubkeyPoint.multiply(currentCommitmentNumber));
// calculate current R(i) = s(i) * H(P(i)) * G + c(i) * I
const currentPubkeyHashpoint = keyHashPoint(currentPubkey);
const currentRightValue = currentPubkeyHashpoint.multiply(currentRandomValue).add(privkeyImage.multiply(currentCommitmentNumber));
commitments[ringIndex(i + 1)] = createCommitment(message, currentLeftValue, currentRightValue);
}
return firstCommitment === commitments[0];
};
const pubkeys = [
'030150cddea7de0bf54aa2da85234937636638a6301246f4c2dab247e103cafa4f',
'02fb012e6d60c4fc2d9d6db605f5a998986991c8d60c107b36268fea586cdace2b',
'0219877ed8cc48ed3ac0b4e0295aaecb3b00dc3c1c49049fc566780d054dec1986',
'039cababc826209b092ac3bd58d807199a4b344d8110999704ed45740d5a963965',
'035051041f8ab6deff643f255220780490a38fcabc1dd8ef588a5ac856aaccd141'
];
const privkey = 'e5d5ca46ab3fe61af6a001e02a5b979ee2c1f205c94804dd575aa6134de43ab3';
const message = 'Arik is rolling his own crypto';
const signature = ringSign(pubkeys, privkey, message);
console.dir(signature);
const verification = verifySignature(pubkeys, message, signature);
console.log(verification);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment