aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGian Demarmels <gian@demarmels.org>2022-01-27 14:42:33 +0100
committerFlorian Dold <florian@dold.me>2022-02-03 01:35:21 +0100
commit003ba5e91bb016caa1d068805723edc3e15f4d30 (patch)
tree4bdc2dae96430b3608b0c33ef20ef173c8e8323c
parent3b10e30ca14e6c18854fe16f750201f37289672b (diff)
- added CS cryptographic routines
-rw-r--r--packages/taler-util/src/talerCrypto.test.ts163
-rw-r--r--packages/taler-util/src/talerCrypto.ts244
2 files changed, 298 insertions, 109 deletions
diff --git a/packages/taler-util/src/talerCrypto.test.ts b/packages/taler-util/src/talerCrypto.test.ts
index 98de2a5a2..350463591 100644
--- a/packages/taler-util/src/talerCrypto.test.ts
+++ b/packages/taler-util/src/talerCrypto.test.ts
@@ -27,13 +27,13 @@ import {
keyExchangeEcdheEddsa,
stringToBytes,
bytesToString,
- hash,
deriveBSeed,
csBlind,
- calcS,
csUnblind,
csVerify,
- CsSignature,
+ scalarMultBase25519,
+ deriveSecrets,
+ calcRBlind,
} from "./talerCrypto.js";
import { sha512, kdf } from "./kdf.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
// init the PRNG manually.
initNodePrng();
+import bigint from "big-integer";
import { AssertionError } from "assert";
test("encoding", (t) => {
@@ -199,6 +200,37 @@ test("taler-exchange-tvg eddsa_ecdh #2", (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 = {
sBlind: Uint8Array;
rPubBlind: Uint8Array;
@@ -206,53 +238,114 @@ test("taler CS blind c", async (t) => {
/**
* CS denomination keypair
*/
- const priv = "9TM70AKDTS57AWY9JK2J4TMBTMW6K62WHHGZWYDG0VM5ABPZKD40";
- const pub = "8GSJZ649T2PXMKZC01Y4ANNBE7MF14QVK9SQEC4E46ZHKCVG8AS0";
+ const priv = "K43QAMEPE9KJJTX6AJZD6N4SN1N3ARVAXZ2MRNPT85FHD4QD2C60";
+ 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
*/
- const rPriv1 = "9TM70AKDTS57AWY9JK2J4TMBTMW6K62WHHGZWYDG0VM5ABPZKD41";
- const rPriv2 = "8TM70AKDTS57AWY9JK2J4TMBTMW6K62WHHGZWYDG0VM5ABPZKD42";
- const rPub1 = nacl.crypto_sign_keyPair_fromSeed(
- decodeCrock(rPriv1),
- ).publicKey;
- const rPub2 = nacl.crypto_sign_keyPair_fromSeed(
- decodeCrock(rPriv2),
- ).publicKey;
- const rPub:[Uint8Array,Uint8Array] = [rPub1, rPub2];
+ const rPriv0 = "B01FJCRCST8JM10K17SJXY7S7HH7T65JMFQ03H6PNYY9Z167Q1T0";
+ const rPriv1 = "N3GW5X6VYSB8PY83CYNHJ3PN6TCA5N5BCS4WT2WEEQH7MTK915P0";
+ const rPub0 = await scalarMultBase25519(decodeCrock(rPriv0));
+ const rPub1 = await scalarMultBase25519(decodeCrock(rPriv1));
+
+ const rPub: [Uint8Array, Uint8Array] = [rPub0, rPub1];
+
+ t.deepEqual(
+ 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 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]);
-
- // Check that derivation is deterministic
- const bseed2 = deriveBSeed(decodeCrock(priv_eddsa), [rPub1, rPub2]);
- t.deepEqual(bseed, bseed2);
+ /**
+ * 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"),
+ );
- const coinPubHash = hash(pub_eddsa);
+ const rBlind = calcRBlind(pub, secrets, rPub);
+ t.deepEqual(
+ rBlind[0],
+ decodeCrock("CHK7JC4SXZ4Y9RDA3881S82F7BP99H35Q361WR6RBXN5YN2ZM1M0"),
+ );
+ t.deepEqual(
+ rBlind[1],
+ decodeCrock("4C65R74GA9PPDX4DC2B948W96T3Z6QEENK2NDJQPNB9QBTKCT590"),
+ );
- const c = await csBlind(bseed, [rPub1, rPub2], decodeCrock(pub), coinPubHash);
+ const c = await csBlind(bseed, rPub, pub, decodeCrock(msg_hash));
+ t.deepEqual(
+ c[0],
+ decodeCrock("F288QXT67TR36E6DHE399G8J24RM6C3DP16HGMH74B6WZ1DETR10"),
+ );
+ t.deepEqual(
+ c[1],
+ decodeCrock("EFK5WTN01NCVS3DZCG20MQDHRHBATRG8589BA0XSZDZ6D0HFR470"),
+ );
- const b = Buffer.from(kdf(1, decodeCrock(priv), new Uint8Array(),new Uint8Array())).readUInt8() % 2;
- if(b !=1 && b !=0){
- throw new AssertionError();
+ const lMod = Array.from(
+ new Uint8Array([
+ 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).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],
};
- const sigblindsig: CsSignature = {
- s: blindsig.sBlind,
- rPub: blindsig.rPubBlind,
- };
- const sig = await csUnblind(bseed,rPub, decodeCrock(pub),b,blindsig);
-
- //const res = await csVerify(coinPubHash, sig, decodeCrock(pub));
+ 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(decodeCrock(msg_hash), sig, pub);
t.deepEqual(res, true);
});
diff --git a/packages/taler-util/src/talerCrypto.ts b/packages/taler-util/src/talerCrypto.ts
index 27a3b3140..934a04e84 100644
--- a/packages/taler-util/src/talerCrypto.ts
+++ b/packages/taler-util/src/talerCrypto.ts
@@ -24,7 +24,7 @@
import * as nacl from "./nacl-fast.js";
import { kdf } from "./kdf.js";
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 { 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,
// just like modern browsers.
// In older versions of node or environments that do not have these
@@ -335,88 +361,153 @@ export type CsBlindSignature = {
rPubBlind: Uint8Array;
};
-type BlindingSecrets = {
- alpha: [Uint8Array,Uint8Array];
- beta: [Uint8Array,Uint8Array];
+export type CsBlindingSecrets = {
+ alpha: [Uint8Array, Uint8Array];
+ beta: [Uint8Array, Uint8Array];
};
-//FIXME: Set correct salt
-function deriveSecrets(bseed: Uint8Array): BlindingSecrets {
- const outLen = 128;
- const salt = "94KPT83PCNS7J83KC5P78Y8";
- const rndout = kdf(outLen, bseed, decodeCrock(salt));
- const secrets: BlindingSecrets = {
- alpha: [rndout.slice(0, 32), rndout.slice(32, 64)],
- beta: [rndout.slice(64, 96), rndout.slice(96, 128)],
+function typedArrayConcat(chunks: Uint8Array[]): Uint8Array {
+ let payloadLen = 0;
+ for (const c of chunks) {
+ payloadLen += c.byteLength;
+ }
+ const buf = new ArrayBuffer(payloadLen);
+ const u8buf = new Uint8Array(buf);
+ 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;
}
-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,
- secrets: BlindingSecrets,
+ secrets: CsBlindingSecrets,
rPub: [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 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 bDp1 = sodium.crypto_scalarmult_ed25519(secrets.beta[1], csPub);
+ const bDp0 = sodium.crypto_scalarmult_ed25519_noclamp(secrets.beta[0], csPub);
+ const bDp1 = sodium.crypto_scalarmult_ed25519_noclamp(secrets.beta[1], csPub);
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 [
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?
-//FIXME: CDash1 is a JS Number array -> are they broken? how to convert bigint back to uint8arrays?
+/**
+ * FDH function used in CS
+ * @param hm message hash
+ * @param rPub public R included in FDH
+ * @param csPub denomination public key as context
+ * @returns mapped Curve25519 scalar
+ */
function csFDH(
hm: Uint8Array,
rPub: Uint8Array,
-) : Uint8Array{
- const lMod = Array.from(new Uint8Array([
- 0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2,
- 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
- ]));
+ csPub: Uint8Array,
+): Uint8Array {
+ const lMod = Array.from(
+ new Uint8Array([
+ 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 res = kdfMod(L, hm, rPub, rPub).toArray(256).value;
- return new Uint8Array(res);
-}
-
-function deriveC(
- hm: Uint8Array,
- rPubDash: [Uint8Array, Uint8Array],
-): [Uint8Array, Uint8Array] {
- const cDash1 = csFDH(hm,rPubDash[0]);
- const cDash2 = csFDH(hm,rPubDash[1]);
- return [cDash1, cDash2];
+ const info = stringToBytes("Curve25519FDH");
+ const preshash = sodium.crypto_hash_sha512(typedArrayConcat([rPub, hm]));
+ return csKdfMod(L, preshash, csPub, info).reverse();
}
-
-//FIXME: Set correct salt and do correct KDF
-// How to do this in one round with Uint8Array?
-// How to pad two ikms correctly?
+/**
+ * blinding seed derived from coin private key
+ * @param coinPriv private key of the corresponding coin
+ * @param rPub public R received from /csr API
+ * @returns blinding seed
+ */
export function deriveBSeed(
coinPriv: Uint8Array,
rPub: [Uint8Array, Uint8Array],
): Uint8Array {
const outLen = 32;
- const salt = "94KPT83PCNS7J83KC5P78Y8";
- const res = kdf(outLen, coinPriv, decodeCrock(salt), rPub[0]);
- return kdf(outLen, res, decodeCrock(salt), rPub[1]);
+ const salt = stringToBytes("b-seed");
+ const ikm = typedArrayConcat([coinPriv, rPub[0], 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(
bseed: Uint8Array,
rPub: [Uint8Array, Uint8Array],
@@ -425,25 +516,24 @@ export async function csBlind(
): Promise<[Uint8Array, Uint8Array]> {
await sodium.ready;
const secrets = deriveSecrets(bseed);
- const rPubDash = calcRDash(csPub, secrets, rPub);
- const c = deriveC(hm, rPubDash);
+ const rPubBlind = calcRBlind(csPub, secrets, rPub);
+ const c_0 = csFDH(hm, rPubBlind[0], csPub);
+ const c_1 = csFDH(hm, rPubBlind[1], csPub);
return [
- 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_0, secrets.beta[0]),
+ sodium.crypto_core_ed25519_scalar_add(c_1, secrets.beta[1]),
];
}
-export async function calcS(
- rPubB: Uint8Array,
- cB: Uint8Array,
- csPriv: Uint8Array,
-): Promise<Uint8Array> {
- await sodium.ready;
- const cBcsPriv = sodium.crypto_core_ed25519_scalar_mul(cB,csPriv);
- return sodium.crypto_core_ed25519_scalar_add(rPubB,cBcsPriv);
-}
-
-//FIXME: Whats an int here??
+/**
+ * Unblind operation to unblind the signature
+ * @param bseed seed to derive secrets
+ * @param rPub public R received from /csr
+ * @param csPub denomination publick key
+ * @param b returned from exchange to select c
+ * @param csSig blinded signature
+ * @returns unblinded signature
+ */
export async function csUnblind(
bseed: Uint8Array,
rPub: [Uint8Array, Uint8Array],
@@ -451,31 +541,37 @@ export async function csUnblind(
b: number,
csSig: CsBlindSignature,
): Promise<CsSignature> {
-
- if(b != 0 && b !=1){
+ if (b != 0 && b != 1) {
throw new AssertionError();
}
await sodium.ready;
const secrets = deriveSecrets(bseed);
- const rPubDash = calcRDash(csPub, secrets, rPub)[b];
- const sig :CsSignature = {
+ const rPubDash = calcRBlind(csPub, secrets, rPub)[b];
+ const sig: CsSignature = {
s: sodium.crypto_core_ed25519_scalar_add(csSig.sBlind, secrets.alpha[b]),
rPub: rPubDash,
};
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(
hm: Uint8Array,
csSig: CsSignature,
csPub: Uint8Array,
): Promise<boolean> {
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 cbDp = sodium.crypto_scalarmult_ed25519_noclamp(cDash,csPub);
- const sGeq = sodium.crypto_core_ed25519_add(csSig.rPub,cbDp);
- return sodium.memcmp(sG,sGeq);
+ const cbDp = sodium.crypto_scalarmult_ed25519_noclamp(cDash, csPub);
+ const sGeq = sodium.crypto_core_ed25519_add(csSig.rPub, cbDp);
+ return sodium.memcmp(sG, sGeq);
}
export interface EddsaKeyPair {