diff options
Diffstat (limited to 'packages/taler-util/src')
-rw-r--r-- | packages/taler-util/src/talerCrypto.test.ts | 163 | ||||
-rw-r--r-- | packages/taler-util/src/talerCrypto.ts | 244 |
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 { |