initial commit for CS routines

This commit is contained in:
Gian Demarmels 2022-01-26 17:09:59 +01:00 committed by Florian Dold
parent 4eb9e48618
commit 3b10e30ca1
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
3 changed files with 225 additions and 1 deletions

View File

@ -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": {

View File

@ -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);
});

View File

@ -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<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??
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 = 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<boolean> {
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;