- added CS cryptographic routines
This commit is contained in:
parent
3b10e30ca1
commit
003ba5e91b
@ -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"));
|
||||||
//const res = await csVerify(coinPubHash, sig, decodeCrock(pub));
|
t.deepEqual(sig.rPub, decodeCrock("CHK7JC4SXZ4Y9RDA3881S82F7BP99H35Q361WR6RBXN5YN2ZM1M0"));
|
||||||
|
|
||||||
|
const res = await csVerify(decodeCrock(msg_hash), sig, pub);
|
||||||
t.deepEqual(res, true);
|
t.deepEqual(res, true);
|
||||||
});
|
});
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user