From 3b10e30ca14e6c18854fe16f750201f37289672b Mon Sep 17 00:00:00 2001 From: Gian Demarmels Date: Wed, 26 Jan 2022 17:09:59 +0100 Subject: initial commit for CS routines --- packages/taler-util/package.json | 4 +- packages/taler-util/src/talerCrypto.test.ts | 67 ++++++++++++ packages/taler-util/src/talerCrypto.ts | 155 ++++++++++++++++++++++++++++ 3 files changed, 225 insertions(+), 1 deletion(-) (limited to 'packages/taler-util') diff --git a/packages/taler-util/package.json b/packages/taler-util/package.json index 56b519de1..694fbcc45 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 e9dfed4da..98de2a5a2 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, + hash, + deriveBSeed, + csBlind, + calcS, + csUnblind, + csVerify, + CsSignature, } from "./talerCrypto.js"; import { sha512, kdf } from "./kdf.js"; import * as nacl from "./nacl-fast.js"; @@ -35,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 { AssertionError } from "assert"; test("encoding", (t) => { const s = "Hello, World"; @@ -189,3 +197,62 @@ test("taler-exchange-tvg eddsa_ecdh #2", (t) => { ); t.deepEqual(encodeCrock(myKm2), key_material); }); + +test("taler CS blind c", async (t) => { + type CsBlindSignature = { + sBlind: Uint8Array; + rPubBlind: Uint8Array; + }; + /** + * CS denomination keypair + */ + const priv = "9TM70AKDTS57AWY9JK2J4TMBTMW6K62WHHGZWYDG0VM5ABPZKD40"; + const pub = "8GSJZ649T2PXMKZC01Y4ANNBE7MF14QVK9SQEC4E46ZHKCVG8AS0"; + + /** + * 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]; + + /** + * Coin key pair + */ + const priv_eddsa = "1KG54M8T3X8BSFSZXCR3SQBSR7Y9P53NX61M864S7TEVMJ2XVPF0"; + const pub_eddsa = eddsaGetPublic(decodeCrock(priv_eddsa)); + + 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); + + const coinPubHash = hash(pub_eddsa); + + const c = await csBlind(bseed, [rPub1, rPub2], decodeCrock(pub), coinPubHash); + + const b = Buffer.from(kdf(1, decodeCrock(priv), new Uint8Array(),new Uint8Array())).readUInt8() % 2; + if(b !=1 && b !=0){ + throw new AssertionError(); + } + const blindsig: CsBlindSignature ={ + sBlind: await calcS(rPub[b],c[b],decodeCrock(priv)), + 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)); + t.deepEqual(res, true); +}); diff --git a/packages/taler-util/src/talerCrypto.ts b/packages/taler-util/src/talerCrypto.ts index 5f1c2e8cc..27a3b3140 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, { compare } 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); @@ -323,6 +325,159 @@ export function rsaVerify( return sig_e.equals(d); } +export type CsSignature = { + s: Uint8Array; + rPub: Uint8Array; +}; + +export type CsBlindSignature = { + sBlind: Uint8Array; + rPubBlind: Uint8Array; +}; + +type BlindingSecrets = { + 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)], + }; + return secrets; +} + +function calcRDash( + csPub: Uint8Array, + secrets: BlindingSecrets, + 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 bDp0 = sodium.crypto_scalarmult_ed25519(secrets.beta[0], csPub); + const bDp1 = sodium.crypto_scalarmult_ed25519(secrets.beta[1], csPub); + + const res0 = sodium.crypto_core_ed25519_add(aG0, bDp0); + const res2 = sodium.crypto_core_ed25519_add(aG2, bDp1); + return [ + sodium.crypto_core_ed25519_add(rPub[0], res0), + sodium.crypto_core_ed25519_add(rPub[1], res2), + ]; +} + +//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? +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, + ])); + 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]; +} + + +//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( + 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]); +} + +export async function csBlind( + bseed: Uint8Array, + rPub: [Uint8Array, Uint8Array], + csPub: Uint8Array, + hm: Uint8Array, +): Promise<[Uint8Array, Uint8Array]> { + await sodium.ready; + const secrets = deriveSecrets(bseed); + const rPubDash = calcRDash(csPub, secrets, rPub); + const c = deriveC(hm, rPubDash); + return [ + 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 { + 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?? +export async function csUnblind( + bseed: Uint8Array, + rPub: [Uint8Array, Uint8Array], + csPub: Uint8Array, + b: number, + csSig: CsBlindSignature, +): Promise { + + 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 = { + s: sodium.crypto_core_ed25519_scalar_add(csSig.sBlind, secrets.alpha[b]), + rPub: rPubDash, + }; + return sig; +} + +export async function csVerify( + hm: Uint8Array, + csSig: CsSignature, + csPub: Uint8Array, +): Promise { + await sodium.ready; + const cDash = csFDH(hm, csSig.rPub); + 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; -- cgit v1.2.3