gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] branch master updated (4eb9e486 -> f5ff4afa)


From: gnunet
Subject: [taler-wallet-core] branch master updated (4eb9e486 -> f5ff4afa)
Date: Thu, 03 Feb 2022 01:35:34 +0100

This is an automated email from the git hooks/post-receive script.

dold pushed a change to branch master
in repository wallet-core.

    from 4eb9e486 version bump
     new 3b10e30c initial commit for CS routines
     new 003ba5e9 - added CS cryptographic routines
     new f5ff4afa clause schnorr

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 packages/taler-util/package.json                   |   4 +-
 packages/taler-util/src/talerCrypto.test.ts        | 160 +++++++++++++
 packages/taler-util/src/talerCrypto.ts             | 266 ++++++++++++++++++++-
 packages/taler-util/src/talerTypes.ts              |  20 +-
 .../src/harness/denomStructures.ts                 |  43 ++--
 packages/taler-wallet-cli/src/harness/harness.ts   |  22 +-
 .../src/integrationtests/test-fee-regression.ts    |   1 +
 .../src/integrationtests/test-revocation.ts        |   3 +-
 .../src/integrationtests/testrunner.ts             |   6 +-
 9 files changed, 498 insertions(+), 27 deletions(-)

diff --git a/packages/taler-util/package.json b/packages/taler-util/package.json
index 56b519de..694fbcc4 100644
--- a/packages/taler-util/package.json
+++ b/packages/taler-util/package.json
@@ -32,6 +32,7 @@
     "pretty": "prettier --write src"
   },
   "devDependencies": {
+    "@types/libsodium-wrappers-sumo": "^0.7.5",
     "@types/node": "^17.0.8",
     "ava": "^4.0.0",
     "esbuild": "^0.14.10",
@@ -42,6 +43,7 @@
   "dependencies": {
     "big-integer": "^1.6.51",
     "jed": "^1.1.1",
+    "libsodium-wrappers-sumo": "^0.7.9",
     "tslib": "^2.3.1"
   },
   "ava": {
@@ -49,4 +51,4 @@
       "lib/*test*"
     ]
   }
-}
\ No newline at end of file
+}
diff --git a/packages/taler-util/src/talerCrypto.test.ts 
b/packages/taler-util/src/talerCrypto.test.ts
index e9dfed4d..35046359 100644
--- a/packages/taler-util/src/talerCrypto.test.ts
+++ b/packages/taler-util/src/talerCrypto.test.ts
@@ -27,6 +27,13 @@ import {
   keyExchangeEcdheEddsa,
   stringToBytes,
   bytesToString,
+  deriveBSeed,
+  csBlind,
+  csUnblind,
+  csVerify,
+  scalarMultBase25519,
+  deriveSecrets,
+  calcRBlind,
 } from "./talerCrypto.js";
 import { sha512, kdf } from "./kdf.js";
 import * as nacl from "./nacl-fast.js";
@@ -35,6 +42,8 @@ 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) => {
   const s = "Hello, World";
@@ -189,3 +198,154 @@ test("taler-exchange-tvg eddsa_ecdh #2", (t) => {
   );
   t.deepEqual(encodeCrock(myKm2), key_material);
 });
+
+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;
+  };
+  /**
+   * CS denomination keypair
+   */
+  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 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"),
+  );
+
+  /**
+   * 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 bseed1 = deriveBSeed(decodeCrock(priv_eddsa), rPub);
+  const bseed2 = deriveBSeed(decodeCrock(priv_eddsa), rPub);
+  t.deepEqual(bseed1, 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 rBlind = calcRBlind(pub, secrets, rPub);
+  t.deepEqual(
+    rBlind[0],
+    decodeCrock("CHK7JC4SXZ4Y9RDA3881S82F7BP99H35Q361WR6RBXN5YN2ZM1M0"),
+  );
+  t.deepEqual(
+    rBlind[1],
+    decodeCrock("4C65R74GA9PPDX4DC2B948W96T3Z6QEENK2NDJQPNB9QBTKCT590"),
+  );
+
+  const c = await csBlind(bseed, rPub, pub, decodeCrock(msg_hash));
+  t.deepEqual(
+    c[0],
+    decodeCrock("F288QXT67TR36E6DHE399G8J24RM6C3DP16HGMH74B6WZ1DETR10"),
+  );
+  t.deepEqual(
+    c[1],
+    decodeCrock("EFK5WTN01NCVS3DZCG20MQDHRHBATRG8589BA0XSZDZ6D0HFR470"),
+  );
+
+  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 b = 0;
+  const blindsig: CsBlindSignature = {
+    sBlind: 
decodeCrock("6KZF904YZA8KK4C8X5JV57E7B84SR8TDDN9GDC8QTRRSNTHJTM4G"),
+    rPubBlind: rPub[b],
+  };
+
+  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 5f1c2e8c..90d52ca7 100644
--- a/packages/taler-util/src/talerCrypto.ts
+++ b/packages/taler-util/src/talerCrypto.ts
@@ -24,7 +24,9 @@
 import * as nacl from "./nacl-fast.js";
 import { kdf } from "./kdf.js";
 import bigint from "big-integer";
+import sodium from "libsodium-wrappers-sumo";
 import { DenominationPubKey, DenomKeyType } from "./talerTypes.js";
+import { AssertionError, equal } from "assert";
 
 export function getRandomBytes(n: number): Uint8Array {
   return nacl.randomBytes(n);
@@ -191,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
@@ -323,6 +351,229 @@ export function rsaVerify(
   return sig_e.equals(d);
 }
 
+export type CsSignature = {
+  s: Uint8Array;
+  rPub: Uint8Array;
+};
+
+export type CsBlindSignature = {
+  sBlind: Uint8Array;
+  rPubBlind: Uint8Array;
+};
+
+export type CsBlindingSecrets = {
+  alpha: [Uint8Array, Uint8Array];
+  beta: [Uint8Array, Uint8Array];
+};
+
+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;
+}
+
+/**
+ * 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: CsBlindingSecrets,
+  rPub: [Uint8Array, Uint8Array],
+): [Uint8Array, Uint8Array] {
+  const aG0 = sodium.crypto_scalarmult_ed25519_base_noclamp(secrets.alpha[0]);
+  const aG1 = sodium.crypto_scalarmult_ed25519_base_noclamp(secrets.alpha[1]);
+
+  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 res1 = sodium.crypto_core_ed25519_add(aG1, bDp1);
+  return [
+    sodium.crypto_core_ed25519_add(rPub[0], res0),
+    sodium.crypto_core_ed25519_add(rPub[1], res1),
+  ];
+}
+
+/**
+ * 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,
+  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 info = stringToBytes("Curve25519FDH");
+  const preshash = sodium.crypto_hash_sha512(typedArrayConcat([rPub, hm]));
+  return csKdfMod(L, preshash, csPub, info).reverse();
+}
+
+/**
+ * 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 = 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],
+  csPub: Uint8Array,
+  hm: Uint8Array,
+): Promise<[Uint8Array, Uint8Array]> {
+  await sodium.ready;
+  const secrets = deriveSecrets(bseed);
+  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]),
+  ];
+}
+
+/**
+ * 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],
+  csPub: Uint8Array,
+  b: number,
+  csSig: CsBlindSignature,
+): Promise<CsSignature> {
+  if (b != 0 && b != 1) {
+    throw new AssertionError();
+  }
+  await sodium.ready;
+  const secrets = deriveSecrets(bseed);
+  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, 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);
+}
+
 export interface EddsaKeyPair {
   eddsaPub: Uint8Array;
   eddsaPriv: Uint8Array;
@@ -365,8 +616,21 @@ export function hashDenomPub(pub: DenominationPubKey): 
Uint8Array {
     return nacl.hash(uint8ArrayBuf);
   } else if (pub.cipher === DenomKeyType.LegacyRsa) {
     return hash(decodeCrock(pub.rsa_public_key));
+  } else if (pub.cipher === DenomKeyType.ClauseSchnorr) {
+    const pubBuf = decodeCrock(pub.cs_public_key);
+    const hashInputBuf = new ArrayBuffer(pubBuf.length + 4 + 4);
+    const uint8ArrayBuf = new Uint8Array(hashInputBuf);
+    const dv = new DataView(hashInputBuf);
+    dv.setUint32(0, pub.age_mask ?? 0);
+    dv.setUint32(4, pub.cipher);
+    uint8ArrayBuf.set(pubBuf, 8);
+    return nacl.hash(uint8ArrayBuf);
   } else {
-    throw Error(`unsupported cipher (${pub.cipher}), unable to hash`);
+    throw Error(
+      `unsupported cipher (${
+        (pub as DenominationPubKey).cipher
+      }), unable to hash`,
+    );
   }
 }
 
diff --git a/packages/taler-util/src/talerTypes.ts 
b/packages/taler-util/src/talerTypes.ts
index 37350c66..7305122b 100644
--- a/packages/taler-util/src/talerTypes.ts
+++ b/packages/taler-util/src/talerTypes.ts
@@ -1123,7 +1123,8 @@ export interface RsaDenominationPubKey {
 
 export interface CsDenominationPubKey {
   cipher: DenomKeyType.ClauseSchnorr;
-  // FIXME: finish definition
+  age_mask: number;
+  cs_public_key: string;
 }
 
 export namespace DenominationPubKey {
@@ -1151,6 +1152,16 @@ export namespace DenominationPubKey {
         return 1;
       }
       return strcmp(p1.rsa_public_key, p2.rsa_public_key);
+    } else if (
+      p1.cipher === DenomKeyType.ClauseSchnorr &&
+      p2.cipher === DenomKeyType.ClauseSchnorr
+    ) {
+      if ((p1.age_mask ?? 0) < (p2.age_mask ?? 0)) {
+        return -1;
+      } else if ((p1.age_mask ?? 0) > (p2.age_mask ?? 0)) {
+        return 1;
+      }
+      return strcmp(p1.cs_public_key, p2.cs_public_key);
     } else {
       throw Error("unsupported cipher");
     }
@@ -1171,6 +1182,7 @@ export const codecForDenominationPubKey = () =>
   buildCodecForUnion<DenominationPubKey>()
     .discriminateOn("cipher")
     .alternative(1, codecForRsaDenominationPubKey())
+    .alternative(2, codecForCsDenominationPubKey())
     .alternative(3, codecForLegacyRsaDenominationPubKey())
     .build("DenominationPubKey");
 
@@ -1186,6 +1198,12 @@ export const codecForLegacyRsaDenominationPubKey = () =>
     .property("rsa_public_key", codecForString())
     .build("LegacyRsaDenominationPubKey");
 
+export const codecForCsDenominationPubKey = () =>
+  buildCodecForObject<CsDenominationPubKey>()
+    .property("cipher", codecForConstNumber(2))
+    .property("cs_public_key", codecForString())
+    .build("CsDenominationPubKey");
+
 export const codecForBankWithdrawalOperationPostResponse =
   (): Codec<BankWithdrawalOperationPostResponse> =>
     buildCodecForObject<BankWithdrawalOperationPostResponse>()
diff --git a/packages/taler-wallet-cli/src/harness/denomStructures.ts 
b/packages/taler-wallet-cli/src/harness/denomStructures.ts
index 5ab9aca0..2ca77703 100644
--- a/packages/taler-wallet-cli/src/harness/denomStructures.ts
+++ b/packages/taler-wallet-cli/src/harness/denomStructures.ts
@@ -14,7 +14,7 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-export interface CoinConfig {
+export interface CoinCoinfigCommon {
   name: string;
   value: string;
   durationWithdraw: string;
@@ -24,10 +24,24 @@ export interface CoinConfig {
   feeDeposit: string;
   feeRefresh: string;
   feeRefund: string;
+}
+
+export interface CoinConfigRsa extends CoinCoinfigCommon {
+  cipher: "RSA";
   rsaKeySize: number;
 }
 
-const coinCommon = {
+/**
+ * Clause Schnorr coin config.
+ */
+export interface CoinConfigCs extends CoinCoinfigCommon {
+  cipher: "CS";
+}
+
+export type CoinConfig = CoinConfigRsa | CoinConfigCs;
+
+const coinRsaCommon = {
+  cipher: "RSA" as const,
   durationLegal: "3 years",
   durationSpend: "2 years",
   durationWithdraw: "7 days",
@@ -35,7 +49,7 @@ const coinCommon = {
 };
 
 export const coin_ct1 = (curr: string): CoinConfig => ({
-  ...coinCommon,
+  ...coinRsaCommon,
   name: `${curr}_ct1`,
   value: `${curr}:0.01`,
   feeDeposit: `${curr}:0.00`,
@@ -45,7 +59,7 @@ export const coin_ct1 = (curr: string): CoinConfig => ({
 });
 
 export const coin_ct10 = (curr: string): CoinConfig => ({
-  ...coinCommon,
+  ...coinRsaCommon,
   name: `${curr}_ct10`,
   value: `${curr}:0.10`,
   feeDeposit: `${curr}:0.01`,
@@ -55,7 +69,7 @@ export const coin_ct10 = (curr: string): CoinConfig => ({
 });
 
 export const coin_u1 = (curr: string): CoinConfig => ({
-  ...coinCommon,
+  ...coinRsaCommon,
   name: `${curr}_u1`,
   value: `${curr}:1`,
   feeDeposit: `${curr}:0.02`,
@@ -65,7 +79,7 @@ export const coin_u1 = (curr: string): CoinConfig => ({
 });
 
 export const coin_u2 = (curr: string): CoinConfig => ({
-  ...coinCommon,
+  ...coinRsaCommon,
   name: `${curr}_u2`,
   value: `${curr}:2`,
   feeDeposit: `${curr}:0.02`,
@@ -75,7 +89,7 @@ export const coin_u2 = (curr: string): CoinConfig => ({
 });
 
 export const coin_u4 = (curr: string): CoinConfig => ({
-  ...coinCommon,
+  ...coinRsaCommon,
   name: `${curr}_u4`,
   value: `${curr}:4`,
   feeDeposit: `${curr}:0.02`,
@@ -85,7 +99,7 @@ export const coin_u4 = (curr: string): CoinConfig => ({
 });
 
 export const coin_u8 = (curr: string): CoinConfig => ({
-  ...coinCommon,
+  ...coinRsaCommon,
   name: `${curr}_u8`,
   value: `${curr}:8`,
   feeDeposit: `${curr}:0.16`,
@@ -95,7 +109,7 @@ export const coin_u8 = (curr: string): CoinConfig => ({
 });
 
 const coin_u10 = (curr: string): CoinConfig => ({
-  ...coinCommon,
+  ...coinRsaCommon,
   name: `${curr}_u10`,
   value: `${curr}:10`,
   feeDeposit: `${curr}:0.2`,
@@ -114,16 +128,6 @@ export const defaultCoinConfig = [
   coin_u10,
 ];
 
-const coinCheapCommon = (curr: string) => ({
-  durationLegal: "3 years",
-  durationSpend: "2 years",
-  durationWithdraw: "7 days",
-  rsaKeySize: 1024,
-  feeRefresh: `${curr}:0.2`,
-  feeRefund: `${curr}:0.2`,
-  feeWithdraw: `${curr}:0.2`,
-});
-
 export function makeNoFeeCoinConfig(curr: string): CoinConfig[] {
   const cc: CoinConfig[] = [];
 
@@ -134,6 +138,7 @@ export function makeNoFeeCoinConfig(curr: string): 
CoinConfig[] {
     const cent = ct % 100;
 
     cc.push({
+      cipher: "RSA",
       durationLegal: "3 years",
       durationSpend: "2 years",
       durationWithdraw: "7 days",
diff --git a/packages/taler-wallet-cli/src/harness/harness.ts 
b/packages/taler-wallet-cli/src/harness/harness.ts
index fc155ba6..fc489327 100644
--- a/packages/taler-wallet-cli/src/harness/harness.ts
+++ b/packages/taler-wallet-cli/src/harness/harness.ts
@@ -430,7 +430,14 @@ function setCoin(config: Configuration, c: CoinConfig) {
   config.setString(s, "fee_withdraw", c.feeWithdraw);
   config.setString(s, "fee_refresh", c.feeRefresh);
   config.setString(s, "fee_refund", c.feeRefund);
-  config.setString(s, "rsa_keysize", `${c.rsaKeySize}`);
+  if (c.cipher === "RSA") {
+    config.setString(s, "rsa_keysize", `${c.rsaKeySize}`);
+    config.setString(s, "cipher", "RSA");
+  } else if (c.cipher === "CS") {
+    config.setString(s, "cipher", "CS");
+  } else {
+    throw new Error();
+  }
 }
 
 /**
@@ -1328,6 +1335,7 @@ export class ExchangeService implements 
ExchangeServiceInterface {
 
   helperCryptoRsaProc: ProcessWrapper | undefined;
   helperCryptoEddsaProc: ProcessWrapper | undefined;
+  helperCryptoCsProc: ProcessWrapper | undefined;
 
   constructor(
     private globalState: GlobalTestState,
@@ -1373,6 +1381,12 @@ export class ExchangeService implements 
ExchangeServiceInterface {
       await cryptoEddsa.wait();
       this.helperCryptoRsaProc = undefined;
     }
+    const cryptoCs = this.helperCryptoCsProc;
+    if (cryptoCs) {
+      cryptoCs.proc.kill("SIGTERM");
+      await cryptoCs.wait();
+      this.helperCryptoCsProc = undefined;
+    }
   }
 
   /**
@@ -1491,6 +1505,12 @@ export class ExchangeService implements 
ExchangeServiceInterface {
       `exchange-crypto-eddsa-${this.name}`,
     );
 
+    this.helperCryptoCsProc = this.globalState.spawnService(
+      "taler-exchange-secmod-cs",
+      ["-c", this.configFilename, "-LDEBUG", ...this.timetravelArgArr],
+      `exchange-crypto-cs-${this.name}`,
+    );
+
     this.helperCryptoRsaProc = this.globalState.spawnService(
       "taler-exchange-secmod-rsa",
       ["-c", this.configFilename, "-LDEBUG", ...this.timetravelArgArr],
diff --git 
a/packages/taler-wallet-cli/src/integrationtests/test-fee-regression.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-fee-regression.ts
index d3ff89ae..aee9fffa 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-fee-regression.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-fee-regression.ts
@@ -76,6 +76,7 @@ export async function createMyTestkudosEnvironment(
   await bank.pingUntilAvailable();
 
   const coinCommon = {
+    cipher: "RSA" as const,
     durationLegal: "3 years",
     durationSpend: "2 years",
     durationWithdraw: "7 days",
diff --git a/packages/taler-wallet-cli/src/integrationtests/test-revocation.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-revocation.ts
index 87c4d958..bf35b538 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-revocation.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-revocation.ts
@@ -27,7 +27,7 @@ import {
   setupDb,
   BankService,
   delayMs,
-  getPayto
+  getPayto,
 } from "../harness/harness.js";
 import {
   withdrawViaBank,
@@ -97,6 +97,7 @@ async function createTestEnvironment(
   await bank.pingUntilAvailable();
 
   const coin_u1: CoinConfig = {
+    cipher: "RSA" as const,
     durationLegal: "3 years",
     durationSpend: "2 years",
     durationWithdraw: "7 days",
diff --git a/packages/taler-wallet-cli/src/integrationtests/testrunner.ts 
b/packages/taler-wallet-cli/src/integrationtests/testrunner.ts
index 98aab75c..84490413 100644
--- a/packages/taler-wallet-cli/src/integrationtests/testrunner.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/testrunner.ts
@@ -14,9 +14,7 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import {
-  minimatch
-} from "@gnu-taler/taler-util";
+import { minimatch } from "@gnu-taler/taler-util";
 import {
   GlobalTestState,
   runTestWithState,
@@ -88,6 +86,7 @@ import { runMerchantSpecPublicOrdersTest } from 
"./test-merchant-spec-public-ord
 import { runExchangeTimetravelTest } from "./test-exchange-timetravel.js";
 import { runDenomUnofferedTest } from "./test-denom-unoffered.js";
 import { runWithdrawalFakebankTest } from "./test-withdrawal-fakebank.js";
+import { runClauseSchnorrTest } from "./test-clause-schnorr.js";
 
 /**
  * Test runner.
@@ -106,6 +105,7 @@ interface TestMainFunction {
 const allTests: TestMainFunction[] = [
   runBankApiTest,
   runClaimLoopTest,
+  runClauseSchnorrTest,
   runDepositTest,
   runDenomUnofferedTest,
   runExchangeManagementTest,

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]