- added CS cryptographic routines

This commit is contained in:
Gian Demarmels 2022-01-27 14:42:33 +01:00 committed by Florian Dold
parent 3b10e30ca1
commit 003ba5e91b
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
2 changed files with 299 additions and 110 deletions

View File

@ -27,13 +27,13 @@ import {
keyExchangeEcdheEddsa, keyExchangeEcdheEddsa,
stringToBytes, stringToBytes,
bytesToString, bytesToString,
hash,
deriveBSeed, deriveBSeed,
csBlind, csBlind,
calcS,
csUnblind, csUnblind,
csVerify, csVerify,
CsSignature, scalarMultBase25519,
deriveSecrets,
calcRBlind,
} from "./talerCrypto.js"; } from "./talerCrypto.js";
import { sha512, kdf } from "./kdf.js"; import { sha512, kdf } from "./kdf.js";
import * as nacl from "./nacl-fast.js"; import * as nacl from "./nacl-fast.js";
@ -42,6 +42,7 @@ import { initNodePrng } from "./prng-node.js";
// Since we import nacl-fast directly (and not via index.node.ts), we need to // Since we import nacl-fast directly (and not via index.node.ts), we need to
// init the PRNG manually. // init the PRNG manually.
initNodePrng(); initNodePrng();
import bigint from "big-integer";
import { AssertionError } from "assert"; import { AssertionError } from "assert";
test("encoding", (t) => { test("encoding", (t) => {
@ -199,6 +200,37 @@ test("taler-exchange-tvg eddsa_ecdh #2", (t) => {
}); });
test("taler CS blind c", async (t) => { test("taler CS blind c", async (t) => {
/**$
* Test Vectors:
{
"operation": "cs_blind_signing",
"message_hash": "KZ7540050MWFPPPJ6C0910TC15AWD6KN6GMK4YH8PY5Z2RKP7NQMHZ1NDD7JHD9CA2CZXDKYN7XRX521YERAF6N50VJZMHWPH18TCFG",
"cs_public_key": "1903SZ7QE1K8T4BHTJ32KDJ153SBXT22DGNQDY5NKJE535J72H2G",
"cs_private_key": "K43QAMEPE9KJJTX6AJZD6N4SN1N3ARVAXZ2MRNPT85FHD4QD2C60",
"cs_nonce": "GWPVFP9160XNADYQZ4T6S7RACB2482KG1JCY0X2Z5R52W74YXY3G",
"cs_r_priv_0": "B01FJCRCST8JM10K17SJXY7S7HH7T65JMFQ03H6PNYY9Z167Q1T0",
"cs_r_priv_1": "N3GW5X6VYSB8PY83CYNHJ3PN6TCA5N5BCS4WT2WEEQH7MTK915P0",
"cs_r_pub_0": "J5XFBKFP9T6BM02H6ZV6Y568PQ2K398MD339036F25XTSP1A7T3G",
"cs_r_pub_1": "GA2CZKJ6CWFS81ZN1T5R4GQFHF7XJV6HWHDR1JA9VATKKXQN89J0",
"cs_bs_alpha_0": "R06FWJ4XEK4JKKKA03JARGD0PD5JAX8DK2N6J0K8CAZZMVQEJ1T0",
"cs_bs_alpha_1": "13NXE2FEHJS0Q5XCWNRF4V1NC3BSAHN6BW02WZ07PG6967156HYG",
"cs_bs_beta_0": "T3EZP42RJQXRTJ4FTDWF18Z422VX7KFGN8GJ3QCCM1QV3N456HD0",
"cs_bs_beta_1": "P3MECYGCCR58QVEDSW443699CDXVT8C8W5ZT22PPNRJ363M72H6G",
"cs_r_pub_blind_0": "CHK7JC4SXZ4Y9RDA3881S82F7BP99H35Q361WR6RBXN5YN2ZM1M0",
"cs_r_pub_blind_1": "4C65R74GA9PPDX4DC2B948W96T3Z6QEENK2NDJQPNB9QBTKCT590",
"cs_c_0": "F288QXT67TR36E6DHE399G8J24RM6C3DP16HGMH74B6WZ1DETR10",
"cs_c_1": "EFK5WTN01NCVS3DZCG20MQDHRHBATRG8589BA0XSZDZ6D0HFR470",
"cs_blind_s": "6KZF904YZA8KK4C8X5JV57E7B84SR8TDDN9GDC8QTRRSNTHJTM4G",
"cs_b": "0000000",
"cs_sig_s": "F4ZKMFW3Q7DFN0N94KAMG2JFFHAC362T0QZ6ZCVZ73RS8P91CR70",
"cs_sig_R": "CHK7JC4SXZ4Y9RDA3881S82F7BP99H35Q361WR6RBXN5YN2ZM1M0",
"cs_c_blind_0": "6TN5454DZCHBDXFAGQFXQY37FNX6YRKW0MPFEX4TG5EHXC98M840",
"cs_c_blind_1": "EX6MYRZX6EC93YB4EE3M7AR3PQDYYG4092917YF29HD36X58NG0G",
"cs_prehash_0": "D29BBP762HEN6ZHZ5T2T6S4VMV400K9Y659M1QQZYZ0WJS3V3EJSF0FVXSCD1E99JJJMW295EY8TEE97YEGSGEQ0Q0A9DDMS2NCAG9R",
"cs_prehash_1": "9BYD02BC29ZF26BG88DWFCCENCS8CD8VZN76XP8JPWKTN9JS73MBCD0F36N0JSM223MRNJZACNYPMW23SGRHYVSP6BTT79GSSK5R228"
}
*/
type CsBlindSignature = { type CsBlindSignature = {
sBlind: Uint8Array; sBlind: Uint8Array;
rPubBlind: Uint8Array; rPubBlind: Uint8Array;
@ -206,53 +238,114 @@ test("taler CS blind c", async (t) => {
/** /**
* CS denomination keypair * CS denomination keypair
*/ */
const priv = "9TM70AKDTS57AWY9JK2J4TMBTMW6K62WHHGZWYDG0VM5ABPZKD40"; const priv = "K43QAMEPE9KJJTX6AJZD6N4SN1N3ARVAXZ2MRNPT85FHD4QD2C60";
const pub = "8GSJZ649T2PXMKZC01Y4ANNBE7MF14QVK9SQEC4E46ZHKCVG8AS0"; const pub_cmp = "1903SZ7QE1K8T4BHTJ32KDJ153SBXT22DGNQDY5NKJE535J72H2G";
const pub = await scalarMultBase25519(decodeCrock(priv));
t.deepEqual(decodeCrock(pub_cmp), pub);
const nonce = "GWPVFP9160XNADYQZ4T6S7RACB2482KG1JCY0X2Z5R52W74YXY3G";
const msg_hash =
"KZ7540050MWFPPPJ6C0910TC15AWD6KN6GMK4YH8PY5Z2RKP7NQMHZ1NDD7JHD9CA2CZXDKYN7XRX521YERAF6N50VJZMHWPH18TCFG";
/** /**
* rPub is returned from the exchange's new /csr API * rPub is returned from the exchange's new /csr API
*/ */
const rPriv1 = "9TM70AKDTS57AWY9JK2J4TMBTMW6K62WHHGZWYDG0VM5ABPZKD41"; const rPriv0 = "B01FJCRCST8JM10K17SJXY7S7HH7T65JMFQ03H6PNYY9Z167Q1T0";
const rPriv2 = "8TM70AKDTS57AWY9JK2J4TMBTMW6K62WHHGZWYDG0VM5ABPZKD42"; const rPriv1 = "N3GW5X6VYSB8PY83CYNHJ3PN6TCA5N5BCS4WT2WEEQH7MTK915P0";
const rPub1 = nacl.crypto_sign_keyPair_fromSeed( const rPub0 = await scalarMultBase25519(decodeCrock(rPriv0));
decodeCrock(rPriv1), const rPub1 = await scalarMultBase25519(decodeCrock(rPriv1));
).publicKey;
const rPub2 = nacl.crypto_sign_keyPair_fromSeed( const rPub: [Uint8Array, Uint8Array] = [rPub0, rPub1];
decodeCrock(rPriv2),
).publicKey; t.deepEqual(
const rPub:[Uint8Array,Uint8Array] = [rPub1, rPub2]; rPub[0],
decodeCrock("J5XFBKFP9T6BM02H6ZV6Y568PQ2K398MD339036F25XTSP1A7T3G"),
);
t.deepEqual(
rPub[1],
decodeCrock("GA2CZKJ6CWFS81ZN1T5R4GQFHF7XJV6HWHDR1JA9VATKKXQN89J0"),
);
/** /**
* Coin key pair * Test if blinding seed derivation is deterministic
* In the wallet the b-seed MUST be different from the Withdraw-Nonce or Refresh Nonce!
* (Eg. derive two different values from coin priv) -> See CS protocols for details
*/ */
const priv_eddsa = "1KG54M8T3X8BSFSZXCR3SQBSR7Y9P53NX61M864S7TEVMJ2XVPF0"; const priv_eddsa = "1KG54M8T3X8BSFSZXCR3SQBSR7Y9P53NX61M864S7TEVMJ2XVPF0";
const pub_eddsa = eddsaGetPublic(decodeCrock(priv_eddsa)); // const pub_eddsa = eddsaGetPublic(decodeCrock(priv_eddsa));
const bseed1 = deriveBSeed(decodeCrock(priv_eddsa), rPub);
const bseed2 = deriveBSeed(decodeCrock(priv_eddsa), rPub);
t.deepEqual(bseed1, bseed2);
const bseed = deriveBSeed(decodeCrock(priv_eddsa), [rPub1, rPub2]); /**
* In this scenario the nonce from the test vectors is used as b-seed and refresh.
* This is only used in testing to test functionality.
* DO NOT USE the same values for blinding-seed and nonce anywhere else.
*
* Tests whether the blinding secrets are derived as in the exchange implementation
*/
const bseed = decodeCrock(nonce);
const secrets = deriveSecrets(bseed);
t.deepEqual(
secrets.alpha[0],
decodeCrock("R06FWJ4XEK4JKKKA03JARGD0PD5JAX8DK2N6J0K8CAZZMVQEJ1T0"),
);
t.deepEqual(
secrets.alpha[1],
decodeCrock("13NXE2FEHJS0Q5XCWNRF4V1NC3BSAHN6BW02WZ07PG6967156HYG"),
);
t.deepEqual(
secrets.beta[0],
decodeCrock("T3EZP42RJQXRTJ4FTDWF18Z422VX7KFGN8GJ3QCCM1QV3N456HD0"),
);
t.deepEqual(
secrets.beta[1],
decodeCrock("P3MECYGCCR58QVEDSW443699CDXVT8C8W5ZT22PPNRJ363M72H6G"),
);
// Check that derivation is deterministic const rBlind = calcRBlind(pub, secrets, rPub);
const bseed2 = deriveBSeed(decodeCrock(priv_eddsa), [rPub1, rPub2]); t.deepEqual(
t.deepEqual(bseed, bseed2); rBlind[0],
decodeCrock("CHK7JC4SXZ4Y9RDA3881S82F7BP99H35Q361WR6RBXN5YN2ZM1M0"),
);
t.deepEqual(
rBlind[1],
decodeCrock("4C65R74GA9PPDX4DC2B948W96T3Z6QEENK2NDJQPNB9QBTKCT590"),
);
const coinPubHash = hash(pub_eddsa); const c = await csBlind(bseed, rPub, pub, decodeCrock(msg_hash));
t.deepEqual(
c[0],
decodeCrock("F288QXT67TR36E6DHE399G8J24RM6C3DP16HGMH74B6WZ1DETR10"),
);
t.deepEqual(
c[1],
decodeCrock("EFK5WTN01NCVS3DZCG20MQDHRHBATRG8589BA0XSZDZ6D0HFR470"),
);
const c = await csBlind(bseed, [rPub1, rPub2], decodeCrock(pub), coinPubHash); const lMod = Array.from(
new Uint8Array([
const b = Buffer.from(kdf(1, decodeCrock(priv), new Uint8Array(),new Uint8Array())).readUInt8() % 2; 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
if(b !=1 && b !=0){ 0x00, 0x00, 0x00, 0x00, 0x14, 0xde, 0xf9, 0xde, 0xa2, 0xf7, 0x9c, 0xd6,
throw new AssertionError(); 0x58, 0x12, 0x63, 0x1a, 0x5c, 0xf5, 0xd3, 0xed,
]),
);
const L = bigint.fromArray(lMod, 256, false).toString();
//Lmod needs to be 2^252+27742317777372353535851937790883648493
if (!L.startsWith("723700")) {
throw new AssertionError({ message: L });
} }
const blindsig: CsBlindSignature ={
sBlind: await calcS(rPub[b],c[b],decodeCrock(priv)), const b = 0;
const blindsig: CsBlindSignature = {
sBlind: decodeCrock("6KZF904YZA8KK4C8X5JV57E7B84SR8TDDN9GDC8QTRRSNTHJTM4G"),
rPubBlind: rPub[b], rPubBlind: rPub[b],
}; };
const sigblindsig: CsSignature = {
s: blindsig.sBlind,
rPub: blindsig.rPubBlind,
};
const sig = await csUnblind(bseed,rPub, decodeCrock(pub),b,blindsig); const sig = await csUnblind(bseed, rPub, pub, b, blindsig);
t.deepEqual(sig.s, decodeCrock("F4ZKMFW3Q7DFN0N94KAMG2JFFHAC362T0QZ6ZCVZ73RS8P91CR70"));
t.deepEqual(sig.rPub, decodeCrock("CHK7JC4SXZ4Y9RDA3881S82F7BP99H35Q361WR6RBXN5YN2ZM1M0"));
//const res = await csVerify(coinPubHash, sig, decodeCrock(pub)); const res = await csVerify(decodeCrock(msg_hash), sig, pub);
t.deepEqual(res, true); t.deepEqual(res, true);
}); });

View File

@ -24,7 +24,7 @@
import * as nacl from "./nacl-fast.js"; import * as nacl from "./nacl-fast.js";
import { kdf } from "./kdf.js"; import { kdf } from "./kdf.js";
import bigint from "big-integer"; import bigint from "big-integer";
import sodium, { compare } from "libsodium-wrappers-sumo"; import sodium from "libsodium-wrappers-sumo";
import { DenominationPubKey, DenomKeyType } from "./talerTypes.js"; import { DenominationPubKey, DenomKeyType } from "./talerTypes.js";
import { AssertionError, equal } from "assert"; import { AssertionError, equal } from "assert";
@ -193,6 +193,32 @@ function kdfMod(
} }
} }
function csKdfMod(
n: bigint.BigInteger,
ikm: Uint8Array,
salt: Uint8Array,
info: Uint8Array,
): Uint8Array {
const nbits = n.bitLength().toJSNumber();
const buflen = Math.floor((nbits - 1) / 8 + 1);
const mask = (1 << (8 - (buflen * 8 - nbits))) - 1;
let counter = 0;
while (true) {
const ctx = new Uint8Array(info.byteLength + 2);
ctx.set(info, 0);
ctx[ctx.length - 2] = (counter >>> 8) & 0xff;
ctx[ctx.length - 1] = counter & 0xff;
const buf = kdf(buflen, ikm, salt, ctx);
const arr = Array.from(buf);
arr[0] = arr[0] & mask;
const r = bigint.fromArray(arr, 256, false);
if (r.lt(n)) {
return new Uint8Array(arr);
}
counter++;
}
}
// Newer versions of node have TextEncoder and TextDecoder as a global, // Newer versions of node have TextEncoder and TextDecoder as a global,
// just like modern browsers. // just like modern browsers.
// In older versions of node or environments that do not have these // In older versions of node or environments that do not have these
@ -335,88 +361,153 @@ export type CsBlindSignature = {
rPubBlind: Uint8Array; rPubBlind: Uint8Array;
}; };
type BlindingSecrets = { export type CsBlindingSecrets = {
alpha: [Uint8Array,Uint8Array]; alpha: [Uint8Array, Uint8Array];
beta: [Uint8Array,Uint8Array]; beta: [Uint8Array, Uint8Array];
}; };
//FIXME: Set correct salt function typedArrayConcat(chunks: Uint8Array[]): Uint8Array {
function deriveSecrets(bseed: Uint8Array): BlindingSecrets { let payloadLen = 0;
const outLen = 128; for (const c of chunks) {
const salt = "94KPT83PCNS7J83KC5P78Y8"; payloadLen += c.byteLength;
const rndout = kdf(outLen, bseed, decodeCrock(salt)); }
const secrets: BlindingSecrets = { const buf = new ArrayBuffer(payloadLen);
alpha: [rndout.slice(0, 32), rndout.slice(32, 64)], const u8buf = new Uint8Array(buf);
beta: [rndout.slice(64, 96), rndout.slice(96, 128)], let p = 0;
for (const c of chunks) {
u8buf.set(c, p);
p += c.byteLength;
}
return u8buf;
}
/**
* Map to scalar subgroup function
* perform clamping as described in RFC7748
* @param scalar
*/
function mtoSS(scalar: Uint8Array): Uint8Array {
scalar[0] &= 248;
scalar[31] &= 127;
scalar[31] |= 64;
return scalar;
}
/**
* The function returns the CS blinding secrets from a seed
* @param bseed seed to derive blinding secrets
* @returns blinding secrets
*/
export function deriveSecrets(bseed: Uint8Array): CsBlindingSecrets {
const outLen = 130;
const salt = stringToBytes("alphabeta");
const rndout = kdf(outLen, bseed, salt);
const secrets: CsBlindingSecrets = {
alpha: [mtoSS(rndout.slice(0, 32)), mtoSS(rndout.slice(64, 96))],
beta: [mtoSS(rndout.slice(32, 64)), mtoSS(rndout.slice(96, 128))],
}; };
return secrets; return secrets;
} }
function calcRDash( /**
* Used for testing, simple scalar multiplication with base point of Cuve25519
* @param s scalar
* @returns new point sG
*/
export async function scalarMultBase25519(s: Uint8Array): Promise<Uint8Array> {
await sodium.ready;
return sodium.crypto_scalarmult_ed25519_base_noclamp(s);
}
/**
* calculation of the blinded public point R in CS
* @param csPub denomination publik key
* @param secrets client blinding secrets
* @param rPub public R received from /csr API
*/
export function calcRBlind(
csPub: Uint8Array, csPub: Uint8Array,
secrets: BlindingSecrets, secrets: CsBlindingSecrets,
rPub: [Uint8Array, Uint8Array], rPub: [Uint8Array, Uint8Array],
): [Uint8Array, Uint8Array] { ): [Uint8Array, Uint8Array] {
//const aG1 = nacl.scalarMult_base();
//const aG2 = nacl.scalarMult_base(secrets.alpha2);
//const bDp1 = nacl.scalarMult(secrets.beta1, csPub);
//const bDp2 = nacl.scalarMult(secrets.beta2, csPub);
const aG0 = sodium.crypto_scalarmult_ed25519_base_noclamp(secrets.alpha[0]); const aG0 = sodium.crypto_scalarmult_ed25519_base_noclamp(secrets.alpha[0]);
const aG2 = sodium.crypto_scalarmult_ed25519_base_noclamp(secrets.alpha[1]); const aG1 = sodium.crypto_scalarmult_ed25519_base_noclamp(secrets.alpha[1]);
const bDp0 = sodium.crypto_scalarmult_ed25519(secrets.beta[0], csPub); const bDp0 = sodium.crypto_scalarmult_ed25519_noclamp(secrets.beta[0], csPub);
const bDp1 = sodium.crypto_scalarmult_ed25519(secrets.beta[1], csPub); const bDp1 = sodium.crypto_scalarmult_ed25519_noclamp(secrets.beta[1], csPub);
const res0 = sodium.crypto_core_ed25519_add(aG0, bDp0); const res0 = sodium.crypto_core_ed25519_add(aG0, bDp0);
const res2 = sodium.crypto_core_ed25519_add(aG2, bDp1); const res1 = sodium.crypto_core_ed25519_add(aG1, bDp1);
return [ return [
sodium.crypto_core_ed25519_add(rPub[0], res0), sodium.crypto_core_ed25519_add(rPub[0], res0),
sodium.crypto_core_ed25519_add(rPub[1], res2), sodium.crypto_core_ed25519_add(rPub[1], res1),
]; ];
} }
//FIXME: How to pad two ikms correctly? /**
//FIXME:_Is kdfMod used correctly? * FDH function used in CS
//FIXME: CDash1 is a JS Number array -> are they broken? how to convert bigint back to uint8arrays? * @param hm message hash
* @param rPub public R included in FDH
* @param csPub denomination public key as context
* @returns mapped Curve25519 scalar
*/
function csFDH( function csFDH(
hm: Uint8Array, hm: Uint8Array,
rPub: Uint8Array, rPub: Uint8Array,
) : Uint8Array{ csPub: Uint8Array,
const lMod = Array.from(new Uint8Array([ ): Uint8Array {
0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, const lMod = Array.from(
0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, new Uint8Array([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
])); 0x00, 0x00, 0x00, 0x00, 0x14, 0xde, 0xf9, 0xde, 0xa2, 0xf7, 0x9c, 0xd6,
0x58, 0x12, 0x63, 0x1a, 0x5c, 0xf5, 0xd3, 0xed,
]),
);
const L = bigint.fromArray(lMod, 256, false); const L = bigint.fromArray(lMod, 256, false);
const res = kdfMod(L, hm, rPub, rPub).toArray(256).value;
return new Uint8Array(res); const info = stringToBytes("Curve25519FDH");
const preshash = sodium.crypto_hash_sha512(typedArrayConcat([rPub, hm]));
return csKdfMod(L, preshash, csPub, info).reverse();
} }
/**
function deriveC( * blinding seed derived from coin private key
hm: Uint8Array, * @param coinPriv private key of the corresponding coin
rPubDash: [Uint8Array, Uint8Array], * @param rPub public R received from /csr API
): [Uint8Array, Uint8Array] { * @returns blinding seed
const cDash1 = csFDH(hm,rPubDash[0]); */
const cDash2 = csFDH(hm,rPubDash[1]);
return [cDash1, cDash2];
}
//FIXME: Set correct salt and do correct KDF
// How to do this in one round with Uint8Array?
// How to pad two ikms correctly?
export function deriveBSeed( export function deriveBSeed(
coinPriv: Uint8Array, coinPriv: Uint8Array,
rPub: [Uint8Array, Uint8Array], rPub: [Uint8Array, Uint8Array],
): Uint8Array { ): Uint8Array {
const outLen = 32; const outLen = 32;
const salt = "94KPT83PCNS7J83KC5P78Y8"; const salt = stringToBytes("b-seed");
const res = kdf(outLen, coinPriv, decodeCrock(salt), rPub[0]); const ikm = typedArrayConcat([coinPriv, rPub[0], rPub[1]]);
return kdf(outLen, res, decodeCrock(salt), rPub[1]); return kdf(outLen, ikm, salt);
} }
/**
* Derive withdraw nonce, used in /csr request
* Note: In withdraw protocol, the nonce is chosen randomly
* @param coinPriv coin private key
* @returns nonce
*/
export function deriveWithdrawNonce(
coinPriv: Uint8Array,
): Uint8Array {
const outLen = 32;
const salt = stringToBytes("n");
return kdf(outLen, coinPriv, salt);
}
/**
* Blind operation for CS signatures, used after /csr call
* @param bseed blinding seed to derive blinding secrets
* @param rPub public R received from /csr
* @param csPub denomination public key
* @param hm message to blind
* @returns two blinded c
*/
export async function csBlind( export async function csBlind(
bseed: Uint8Array, bseed: Uint8Array,
rPub: [Uint8Array, Uint8Array], rPub: [Uint8Array, Uint8Array],
@ -425,25 +516,24 @@ export async function csBlind(
): Promise<[Uint8Array, Uint8Array]> { ): Promise<[Uint8Array, Uint8Array]> {
await sodium.ready; await sodium.ready;
const secrets = deriveSecrets(bseed); const secrets = deriveSecrets(bseed);
const rPubDash = calcRDash(csPub, secrets, rPub); const rPubBlind = calcRBlind(csPub, secrets, rPub);
const c = deriveC(hm, rPubDash); const c_0 = csFDH(hm, rPubBlind[0], csPub);
const c_1 = csFDH(hm, rPubBlind[1], csPub);
return [ return [
sodium.crypto_core_ed25519_scalar_add(c[0], secrets.beta[0]), sodium.crypto_core_ed25519_scalar_add(c_0, secrets.beta[0]),
sodium.crypto_core_ed25519_scalar_add(c[1], secrets.beta[1]), sodium.crypto_core_ed25519_scalar_add(c_1, secrets.beta[1]),
]; ];
} }
export async function calcS( /**
rPubB: Uint8Array, * Unblind operation to unblind the signature
cB: Uint8Array, * @param bseed seed to derive secrets
csPriv: Uint8Array, * @param rPub public R received from /csr
): Promise<Uint8Array> { * @param csPub denomination publick key
await sodium.ready; * @param b returned from exchange to select c
const cBcsPriv = sodium.crypto_core_ed25519_scalar_mul(cB,csPriv); * @param csSig blinded signature
return sodium.crypto_core_ed25519_scalar_add(rPubB,cBcsPriv); * @returns unblinded signature
} */
//FIXME: Whats an int here??
export async function csUnblind( export async function csUnblind(
bseed: Uint8Array, bseed: Uint8Array,
rPub: [Uint8Array, Uint8Array], rPub: [Uint8Array, Uint8Array],
@ -451,31 +541,37 @@ export async function csUnblind(
b: number, b: number,
csSig: CsBlindSignature, csSig: CsBlindSignature,
): Promise<CsSignature> { ): Promise<CsSignature> {
if (b != 0 && b != 1) {
if(b != 0 && b !=1){
throw new AssertionError(); throw new AssertionError();
} }
await sodium.ready; await sodium.ready;
const secrets = deriveSecrets(bseed); const secrets = deriveSecrets(bseed);
const rPubDash = calcRDash(csPub, secrets, rPub)[b]; const rPubDash = calcRBlind(csPub, secrets, rPub)[b];
const sig :CsSignature = { const sig: CsSignature = {
s: sodium.crypto_core_ed25519_scalar_add(csSig.sBlind, secrets.alpha[b]), s: sodium.crypto_core_ed25519_scalar_add(csSig.sBlind, secrets.alpha[b]),
rPub: rPubDash, rPub: rPubDash,
}; };
return sig; return sig;
} }
/**
* Verification algorithm for CS signatures
* @param hm message signed
* @param csSig unblinded signature
* @param csPub denomination publick key
* @returns true if valid, false if unvalid
*/
export async function csVerify( export async function csVerify(
hm: Uint8Array, hm: Uint8Array,
csSig: CsSignature, csSig: CsSignature,
csPub: Uint8Array, csPub: Uint8Array,
): Promise<boolean> { ): Promise<boolean> {
await sodium.ready; await sodium.ready;
const cDash = csFDH(hm, csSig.rPub); const cDash = csFDH(hm, csSig.rPub, csPub);
const sG = sodium.crypto_scalarmult_ed25519_base_noclamp(csSig.s); const sG = sodium.crypto_scalarmult_ed25519_base_noclamp(csSig.s);
const cbDp = sodium.crypto_scalarmult_ed25519_noclamp(cDash,csPub); const cbDp = sodium.crypto_scalarmult_ed25519_noclamp(cDash, csPub);
const sGeq = sodium.crypto_core_ed25519_add(csSig.rPub,cbDp); const sGeq = sodium.crypto_core_ed25519_add(csSig.rPub, cbDp);
return sodium.memcmp(sG,sGeq); return sodium.memcmp(sG, sGeq);
} }
export interface EddsaKeyPair { export interface EddsaKeyPair {