JS-only crypto (only primitives so far)

This commit is contained in:
Florian Dold 2019-11-27 17:59:51 +01:00
parent d42b9e3df8
commit c3ca556aff
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
14 changed files with 1782 additions and 940 deletions

View File

@ -63,6 +63,7 @@
"@types/chrome": "^0.0.91", "@types/chrome": "^0.0.91",
"@types/urijs": "^1.19.3", "@types/urijs": "^1.19.3",
"axios": "^0.19.0", "axios": "^0.19.0",
"big-integer": "^1.6.48",
"idb-bridge": "^0.0.14", "idb-bridge": "^0.0.14",
"qrcode-generator": "^1.4.3", "qrcode-generator": "^1.4.3",
"source-map-support": "^0.5.12", "source-map-support": "^0.5.12",

View File

@ -45,7 +45,7 @@ import * as native from "./emscInterface";
import { AmountJson } from "../amounts"; import { AmountJson } from "../amounts";
import * as Amounts from "../amounts"; import * as Amounts from "../amounts";
import * as timer from "../timer"; import * as timer from "../timer";
import { getRandomBytes, encodeCrock } from "./nativeCrypto"; import { getRandomBytes, encodeCrock } from "./talerCrypto";
export class CryptoImplementation { export class CryptoImplementation {
static enableTracing: boolean = false; static enableTracing: boolean = false;

View File

@ -20,8 +20,7 @@ import test from "ava";
import { NodeEmscriptenLoader } from "./nodeEmscriptenLoader"; import { NodeEmscriptenLoader } from "./nodeEmscriptenLoader";
import * as native from "./emscInterface"; import * as native from "./emscInterface";
import nacl = require("./nacl-fast"); import { encodeCrock, decodeCrock } from "./talerCrypto";
import { encodeCrock, decodeCrock } from "./nativeCrypto";
import { timestampCheck } from "../helpers"; import { timestampCheck } from "../helpers";
@ -37,12 +36,7 @@ test("string hashing", async (t) => {
const te = new TextEncoder(); const te = new TextEncoder();
const x2 = te.encode("hello taler\0") const x2 = te.encode("hello taler\0");
const hc2 = encodeCrock(nacl.hash(x2));
console.log(`# hc2 ${hc}`);
t.true(h === hc2);
t.pass(); t.pass();
}); });
@ -68,29 +62,8 @@ test("signing", async (t) => {
console.timeEnd("a"); console.timeEnd("a");
t.true(native.eddsaVerify(native.SignaturePurpose.TEST, purpose, sig, pub)); t.true(native.eddsaVerify(native.SignaturePurpose.TEST, purpose, sig, pub));
console.log("priv size", decodeCrock(privCrock).byteLength);
const pair = nacl.sign_keyPair_fromSeed(new Uint8Array(decodeCrock(privCrock)));
console.log("emsc priv", privCrock);
console.log("emsc pub", pubCrock);
console.log("nacl priv", encodeCrock(pair.secretKey));
console.log("nacl pub", encodeCrock(pair.publicKey));
const d2 = new Uint8Array(decodeCrock(purposeDataCrock)); const d2 = new Uint8Array(decodeCrock(purposeDataCrock));
const d3 = nacl.hash(d2);
console.time("b");
for (let i = 0; i < 5000; i++) {
const s2 = nacl.sign_detached(d3, pair.secretKey);
}
console.timeEnd("b");
const s2 = nacl.sign_detached(d3, pair.secretKey);
console.log("sig1:", sig.toCrock()); console.log("sig1:", sig.toCrock());
console.log("sig2:", encodeCrock(s2));
t.pass(); t.pass();
}); });

View File

@ -17,8 +17,6 @@
import nacl = require("./nacl-fast"); import nacl = require("./nacl-fast");
import { sha256 } from "./sha256"; import { sha256 } from "./sha256";
let createHmac: any;
export function sha512(data: Uint8Array): Uint8Array { export function sha512(data: Uint8Array): Uint8Array {
return nacl.hash(data); return nacl.hash(data);
} }
@ -32,7 +30,6 @@ export function hmac(
if (key.byteLength > blockSize) { if (key.byteLength > blockSize) {
key = digest(key); key = digest(key);
} }
console.log("message", message);
if (key.byteLength < blockSize) { if (key.byteLength < blockSize) {
const k = key; const k = key;
key = new Uint8Array(blockSize); key = new Uint8Array(blockSize);
@ -62,39 +59,34 @@ export function hmacSha256(key: Uint8Array, message: Uint8Array) {
return hmac(sha256, 64, key, message); return hmac(sha256, 64, key, message);
} }
/* export function kdf(
function expand(prfAlgo: string, prk: Uint8Array, length: number, info: Uint8Array) { outputLength: number,
let hashLength; ikm: Uint8Array,
if (prfAlgo == "sha512") { salt: Uint8Array,
hashLength = 64; info: Uint8Array,
} else if (prfAlgo == "sha256") { ): Uint8Array {
hashLength = 32;
} else {
throw Error("unsupported hash");
}
info = info || Buffer.alloc(0);
var N = Math.ceil(length / hashLength);
var memo: Buffer[] = [];
for (var i = 0; i < N; i++) {
memo[i] = createHmac(prfAlgo, prk)
.update(memo[i - 1] || Buffer.alloc(0))
.update(info)
.update(Buffer.alloc(1, i + 1))
.digest();
}
return Buffer.concat(memo, length);
}
*/
export function kdf(ikm: Uint8Array, salt: Uint8Array, info: Uint8Array) {
// extract // extract
const prk = hmacSha512(salt, ikm); const prk = hmacSha512(salt, ikm);
// expand // expand
const N = Math.ceil(outputLength / 32);
const output = new Uint8Array(N * 32);
for (let i = 0; i < N; i++) {
let buf;
if (i == 0) {
buf = new Uint8Array(info.byteLength + 1);
buf.set(info, 0);
} else {
buf = new Uint8Array(info.byteLength + 1 + 32);
for (let j = 0; j < 32; j++) {
buf[j] = output[(i - 1) * 32 + j];
}
buf.set(info, 32);
}
buf[buf.length - 1] = i + 1;
const chunk = hmacSha256(prk, buf);
output.set(chunk, i * 32);
}
var N = Math.ceil(length / 256); return output;
//return expand(prfAlgo, prk, length, info);
return prk;
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,85 +0,0 @@
/*
This file is part of GNU Taler
(C) 2019 GNUnet e.V.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
* Imports
*/
import test from "ava";
import { encodeCrock, decodeCrock } from "./nativeCrypto";
import { hmacSha512, sha512 } from "./kdf";
import nacl = require("./nacl-fast");
function hexToBytes(hex: string) {
for (var bytes = [], c = 0; c < hex.length; c += 2)
bytes.push(parseInt(hex.substr(c, 2), 16));
return bytes;
}
function bytesToHex(bytes: Uint8Array): string {
for (var hex = [], i = 0; i < bytes.length; i++) {
var current = bytes[i] < 0 ? bytes[i] + 256 : bytes[i];
hex.push((current >>> 4).toString(16));
hex.push((current & 0xf).toString(16));
}
return hex.join("");
}
test("encoding", t => {
const utf8decoder = new TextDecoder("utf-8");
const utf8encoder = new TextEncoder();
const s = "Hello, World";
const encStr = encodeCrock(utf8encoder.encode(s));
const outBuf = decodeCrock(encStr);
const sOut = utf8decoder.decode(outBuf);
t.deepEqual(s, sOut);
});
test("taler-exchange-tvg hash code", t => {
const input = "91JPRV3F5GG4EKJN41A62V35E8";
const output =
"CW96WR74JS8T53EC8GKSGD49QKH4ZNFTZXDAWMMV5GJ1E4BM6B8GPN5NVHDJ8ZVXNCW7Q4WBYCV61HCA3PZC2YJD850DT29RHHN7ESR";
const myOutput = encodeCrock(sha512(decodeCrock(input)));
t.deepEqual(myOutput, output);
});
test("taler-exchange-tvg ecdhe key", t => {
const priv1 = "YSYA38XH1PH40ZPSEZCXEFX9PH9Q3A2PE19FHM54DMTZ4MAPH9S0";
const pub1 = "GNQRNSYF4BT4V0EV0DBXZCHFVQ79ATP0KBJ9EAY18FGSY513A5VG";
const myPub = nacl.x25519_edwards_keyPair_fromSecretKey(decodeCrock(priv1))
t.deepEqual(encodeCrock(myPub), pub1);
//const myPub1 = nacl.scalarMult.base(decodeCrock(priv1));
//t.deepEqual(encodeCrock(myPub1), pub1);
//const p = nacl.box.keyPair.fromSecretKey(decodeCrock(priv1))
//t.deepEqual(encodeCrock(p.publicKey), pub1);
//const r = nacl.scalarMult(decodeCrock(priv2), decodeCrock(pub1));
//t.deepEqual(encodeCrock(nacl.hash(r)), skm);
//const mySkm = nacl.
});
test("taler-exchange-tvg eddsa key", t => {
const priv = "H2JGQ2T3A5WBC5QV3YRFE31AMRGF2F9WPXZ03EM3NS3PYHM80WA0";
const pub = "QFGMB2WTPYXMXZRPFYFEM2VMQ028M71JMECF31P3J8VC3SCJ777G";
const pair = nacl.sign_keyPair_fromSeed(decodeCrock(priv));
t.deepEqual(encodeCrock(pair.publicKey), pub);
});

View File

@ -1,140 +0,0 @@
/*
This file is part of GNU Taler
(C) 2019 GNUnet e.V.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
* Native implementation of GNU Taler crypto.
*/
let isNode;
let myGetRandom: (n: number) => ArrayBuffer;
if (require) {
// node.js
const cr = require("crypto");
myGetRandom = (n: number) => {
const buf = cr.randomBytes(n);
return Uint8Array.from(buf);
}
} else {
// Browser
myGetRandom = (n: number) => {
const ret = new Uint8Array(n);
window.crypto.getRandomValues(ret);
return ret;
}
}
export function getRandomBytes(n: number): ArrayBuffer {
return myGetRandom(n);
}
const encTable = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
class EncodingError extends Error {
constructor() {
super("Encoding error");
Object.setPrototypeOf(this, EncodingError.prototype);
}
}
function getValue(chr: string): number {
let a = chr;
switch (chr) {
case "O":
case "o":
a = "0;";
break;
case "i":
case "I":
case "l":
case "L":
a = "1";
break;
case "u":
case "U":
a = "V";
}
if (a >= "0" && a <= "9") {
return a.charCodeAt(0) - "0".charCodeAt(0);
}
if (a >= "a" && a <= "z") a = a.toUpperCase();
let dec = 0;
if (a >= "A" && a <= "Z") {
if ("I" < a) dec++;
if ("L" < a) dec++;
if ("O" < a) dec++;
if ("U" < a) dec++;
return a.charCodeAt(0) - "A".charCodeAt(0) + 10 - dec;
}
throw new EncodingError();
}
export function encodeCrock(data: ArrayBuffer): string {
const dataBytes = new Uint8Array(data);
let sb = "";
const size = data.byteLength;
let bitBuf = 0;
let numBits = 0;
let pos = 0;
while (pos < size || numBits > 0) {
if (pos < size && numBits < 5) {
const d = dataBytes[pos++];
bitBuf = (bitBuf << 8) | d;
numBits += 8;
}
if (numBits < 5) {
// zero-padding
bitBuf = bitBuf << (5 - numBits);
numBits = 5;
}
const v = (bitBuf >>> (numBits - 5)) & 31;
sb += encTable[v];
numBits -= 5;
}
return sb;
}
export function decodeCrock(encoded: string): Uint8Array {
const size = encoded.length;
let bitpos = 0;
let bitbuf = 0;
let readPosition = 0;
const outLen = Math.floor((size * 5) / 8);
const out = new Uint8Array(outLen);
let outPos = 0;
while (readPosition < size || bitpos > 0) {
//println("at position $readPosition with bitpos $bitpos")
if (readPosition < size) {
const v = getValue(encoded[readPosition++]);
bitbuf = (bitbuf << 5) | v;
bitpos += 5;
}
while (bitpos >= 8) {
const d = (bitbuf >>> (bitpos - 8)) & 0xff;
out[outPos++] = d;
bitpos -= 8;
}
if (readPosition == size && bitpos > 0) {
bitbuf = (bitbuf << (8 - bitpos)) & 0xff;
bitpos = bitbuf == 0 ? 0 : 8;
}
}
return out;
}

View File

@ -0,0 +1,161 @@
/*
This file is part of GNU Taler
(C) 2019 GNUnet e.V.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
* Imports
*/
import test from "ava";
import {
encodeCrock,
decodeCrock,
ecdheGetPublic,
eddsaGetPublic,
keyExchangeEddsaEcdhe,
keyExchangeEcdheEddsa,
rsaBlind,
rsaUnblind,
rsaVerify,
} from "./talerCrypto";
import { hmacSha512, sha512, kdf } from "./kdf";
import nacl = require("./nacl-fast");
function hexToBytes(hex: string) {
for (var bytes = [], c = 0; c < hex.length; c += 2)
bytes.push(parseInt(hex.substr(c, 2), 16));
return bytes;
}
function bytesToHex(bytes: Uint8Array): string {
for (var hex = [], i = 0; i < bytes.length; i++) {
var current = bytes[i] < 0 ? bytes[i] + 256 : bytes[i];
hex.push((current >>> 4).toString(16));
hex.push((current & 0xf).toString(16));
}
return hex.join("");
}
test("encoding", t => {
const utf8decoder = new TextDecoder("utf-8");
const utf8encoder = new TextEncoder();
const s = "Hello, World";
const encStr = encodeCrock(utf8encoder.encode(s));
const outBuf = decodeCrock(encStr);
const sOut = utf8decoder.decode(outBuf);
t.deepEqual(s, sOut);
});
test("taler-exchange-tvg hash code", t => {
const input = "91JPRV3F5GG4EKJN41A62V35E8";
const output =
"CW96WR74JS8T53EC8GKSGD49QKH4ZNFTZXDAWMMV5GJ1E4BM6B8GPN5NVHDJ8ZVXNCW7Q4WBYCV61HCA3PZC2YJD850DT29RHHN7ESR";
const myOutput = encodeCrock(sha512(decodeCrock(input)));
t.deepEqual(myOutput, output);
});
test("taler-exchange-tvg ecdhe key", t => {
const priv1 = "X4T4N0M8PVQXQEBW2BA7049KFSM7J437NSDFC6GDNM3N5J9367A0";
const pub1 = "M997P494MS6A95G1P0QYWW2VNPSHSX5Q6JBY5B9YMNYWP0B50X3G";
const priv2 = "14A0MMQ64DCV8HE0CS3WBC9DHFJAHXRGV7NEARFJPC5R5E1697E0";
const skm =
"NXRY2YCY7H9B6KM928ZD55WG964G59YR0CPX041DYXKBZZ85SAWNPQ8B30QRM5FMHYCXJAN0EAADJYWEF1X3PAC2AJN28626TR5A6AR";
const myPub1 = nacl.scalarMult_base(decodeCrock(priv1));
t.deepEqual(encodeCrock(myPub1), pub1);
const mySkm = nacl.hash(
nacl.scalarMult(decodeCrock(priv2), decodeCrock(pub1)),
);
t.deepEqual(encodeCrock(mySkm), skm);
});
test("taler-exchange-tvg eddsa key", t => {
const priv = "9TM70AKDTS57AWY9JK2J4TMBTMW6K62WHHGZWYDG0VM5ABPZKD40";
const pub = "8GSJZ649T2PXMKZC01Y4ANNBE7MF14QVK9SQEC4E46ZHKCVG8AS0";
const pair = nacl.sign_keyPair_fromSeed(decodeCrock(priv));
t.deepEqual(encodeCrock(pair.publicKey), pub);
});
test("taler-exchange-tvg kdf", t => {
const salt = "94KPT83PCNS7J83KC5P78Y8";
const ikm = "94KPT83MD1JJ0WV5CDS6AX10D5Q70XBM41NPAY90DNGQ8SBJD5GPR";
const ctx =
"94KPT83141HPYVKMCNW78833D1TPWTSC41GPRWVF41NPWVVQDRG62WS04XMPWSKF4WG6JVH0EHM6A82J8S1G";
const outLen = 64;
const out =
"GTMR4QT05Z9WF5HKVG0WK9RPXGHSMHJNW377G9GJXCA8B0FEKPF4D27RJMSJZYWSQNTBJ5EYVV7ZW18B48Z0JVJJ80RHB706Y96Q358";
const myOut = kdf(
outLen,
decodeCrock(ikm),
decodeCrock(salt),
decodeCrock(ctx),
);
t.deepEqual(encodeCrock(myOut), out);
});
test("taler-exchange-tvg eddsa_ecdh", t => {
const priv_ecdhe = "4AFZWMSGTVCHZPQ0R81NWXDCK4N58G7SDBBE5KXE080Y50370JJG";
const pub_ecdhe = "FXFN5GPAFTKVPWJDPVXQ87167S8T82T5ZV8CDYC0NH2AE14X0M30";
const priv_eddsa = "1KG54M8T3X8BSFSZXCR3SQBSR7Y9P53NX61M864S7TEVMJ2XVPF0";
const pub_eddsa = "7BXWKG6N224C57RTDV8XEAHR108HG78NMA995BE8QAT5GC1S7E80";
const key_material =
"PKZ42Z56SVK2796HG1QYBRJ6ZQM2T9QGA3JA4AAZ8G7CWK9FPX175Q9JE5P0ZAX3HWWPHAQV4DPCK10R9X3SAXHRV0WF06BHEC2ZTKR";
const myEcdhePub = ecdheGetPublic(decodeCrock(priv_ecdhe));
t.deepEqual(encodeCrock(myEcdhePub), pub_ecdhe);
const myEddsaPub = eddsaGetPublic(decodeCrock(priv_eddsa));
t.deepEqual(encodeCrock(myEddsaPub), pub_eddsa);
const myKm1 = keyExchangeEddsaEcdhe(
decodeCrock(priv_eddsa),
decodeCrock(pub_ecdhe),
);
t.deepEqual(encodeCrock(myKm1), key_material);
const myKm2 = keyExchangeEcdheEddsa(
decodeCrock(priv_ecdhe),
decodeCrock(pub_eddsa),
);
t.deepEqual(encodeCrock(myKm2), key_material);
});
test("taler-exchange-tvg blind signing", t => {
const messageHash =
"TT1R28D79EJEJ9PC35AQS35CCG85DSXSZ508MV2HS2FN4ME6AHESZX5WP485R8A75KG53FN6F1YNW95008663TKAPWB81420VG17BY8";
const rsaPublicKey =
"040000Y62RSDDKZXTE7GDVA302ZZR0DY224RSDT6WDWR1XGT8E3YG80XV6TMT3ZCNP8XC84W0N6MSZ0EF8S3YB1JJ2AXY9JQZW3MCA0CG38ER4YE2RY4Q2666DEZSNKT29V6CKZVCDHXSAKY8W6RPEKEQ5YSBYQK23MRK3CQTNNJXQFDKEMRHEC5Y6RDHAC5RJCV8JJ8BF18VPKZ2Q7BB14YN1HJ22H8EZGW0RDGG9YPEWA9183BHEQ651PP81J514TJ9K8DH23AJ50SZFNS429HQ390VRP5E4MQ7RK7ZJXXTSZAQSRTC0QF28P23PD37C17QFQB0BBC54MB8MDH7RW104STG6VN0J22P39JP4EXPVGK5D9AX5W869MDQ6SRD42ZYK5H20227Q8CCWSQ6C3132WP0F0H04002";
const bks = "7QD31RPJH0W306RJWBRG646Z2FTA1F89BKSXPDAG7YM0N5Z0B610";
const bm =
"GA8PC6YH9VF5MW6P2DKTV0W0ZTQ24DZ9EAN5QH3SQXRH7SCZHFMM21ZY05F0BS7MFW8TSEP4SEB280BYP5ACHNQWGE10PCXDDMK7ECXJDPHJ224JBCV4KYNWG6NBR3SC9HK8FXVFX55GFBJFNQHNZGEB8DB0KN9MSVYFDXN45KPMSNY03FVX0JZ0R3YG9XQ8XVGB5SYZCF0QSHWH61MT0Q10CZD2V114BT64D3GD86EJ5S9WBMYG51SDN5CSKEJ734YAJ4HCEWW0RDN8GXA9ZMA18SKVW8T3TTBCPJRF2Y77JGQ08GF35SYGA2HWFV1HGVS8RCTER6GB9SZHRG4T7919H9C1KFAP50G2KSV6X42D6KNJANNSGKQH649TJ00YJQXPHPNFBSS198RY2C243D4B4W";
const bs =
"5VW0MS5PRBA3W8TPATSTDA2YRFQM1Z7F2DWKQ8ATMZYYY768Q3STZ3HGNVYQ6JB5NKP80G5HGE58616FPA70SX9PTW7EN8EJ23E26FASBWZBP8E2RWQQ5E0F72B2PWRP5ZCA2J3AB3F6P86XK4PZYT64RF94MDGHY0GSDSSBH5YSFB3VM0KVXA52H2Y2G9S85AVCSD3BTMHQRF5BJJ8JE00T4GK70PSTVCGMRKRNA7DGW7GD2F35W55AXF7R2YJC8PAGNSJYWKC3PC75A5N8H69K299AK5PM3CDDHNS4BMRNGF7K49CR4ZBFRXDAWMB3X6T05Q4NKSG0F1KP5JA0XBMF2YJK7KEPRD1EWCHJE44T9YXBTK4W9CV77X7Z9P407ZC6YB3M2ARANZXHJKSM3XC33M";
const sig =
"PFT6WQJGCM9DE6264DJS6RMG4XDMCDBJKZGSXAF3BEXWZ979Q13NETKK05S1YV91CX3Y034FSS86SSHZTTE8097RRESQP52EKFGTWJXKHZJEQJ49YHMBNQDHW4CFBJECNJSV2PMHWVGXV7HB84R6P0S3ES559HWQX01Q9MYDEGRNHKW87QR2BNSG951D5NQGAKEJ2SSJBE18S6WYAC24FAP8TT8ANECH5371J0DJY0YR0VWAFWVJDV8XQSFXWMJ80N3A80SPSHPYJY3WZZXW63WQ46WHYY56ZSNE5G1RZ5CR0XYV2ECKPM8R0FS58EV16WTRAM1ABBFVNAT3CAEFAZCWP3XHPVBQY5NZVTD5QS2Q8SKJQ2XB30E11CWDN9KTV5CBK4DN72EVG73F3W3BATAKHG";
const myBm = rsaBlind(decodeCrock(messageHash), decodeCrock(bks), decodeCrock(rsaPublicKey));
t.deepEqual(encodeCrock(myBm), bm);
const mySig = rsaUnblind(decodeCrock(bs), decodeCrock(rsaPublicKey), decodeCrock(bks));
t.deepEqual(encodeCrock(mySig), sig);
const v = rsaVerify(decodeCrock(messageHash), decodeCrock(sig), decodeCrock(rsaPublicKey));
t.true(v);
});

277
src/crypto/talerCrypto.ts Normal file
View File

@ -0,0 +1,277 @@
/*
This file is part of GNU Taler
(C) 2019 GNUnet e.V.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
* Native implementation of GNU Taler crypto.
*/
import nacl = require("./nacl-fast");
import bigint from "big-integer";
import { kdf } from "./kdf";
export function getRandomBytes(n: number): Uint8Array {
return nacl.randomBytes(n);
}
const encTable = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
class EncodingError extends Error {
constructor() {
super("Encoding error");
Object.setPrototypeOf(this, EncodingError.prototype);
}
}
function getValue(chr: string): number {
let a = chr;
switch (chr) {
case "O":
case "o":
a = "0;";
break;
case "i":
case "I":
case "l":
case "L":
a = "1";
break;
case "u":
case "U":
a = "V";
}
if (a >= "0" && a <= "9") {
return a.charCodeAt(0) - "0".charCodeAt(0);
}
if (a >= "a" && a <= "z") a = a.toUpperCase();
let dec = 0;
if (a >= "A" && a <= "Z") {
if ("I" < a) dec++;
if ("L" < a) dec++;
if ("O" < a) dec++;
if ("U" < a) dec++;
return a.charCodeAt(0) - "A".charCodeAt(0) + 10 - dec;
}
throw new EncodingError();
}
export function encodeCrock(data: ArrayBuffer): string {
const dataBytes = new Uint8Array(data);
let sb = "";
const size = data.byteLength;
let bitBuf = 0;
let numBits = 0;
let pos = 0;
while (pos < size || numBits > 0) {
if (pos < size && numBits < 5) {
const d = dataBytes[pos++];
bitBuf = (bitBuf << 8) | d;
numBits += 8;
}
if (numBits < 5) {
// zero-padding
bitBuf = bitBuf << (5 - numBits);
numBits = 5;
}
const v = (bitBuf >>> (numBits - 5)) & 31;
sb += encTable[v];
numBits -= 5;
}
return sb;
}
export function decodeCrock(encoded: string): Uint8Array {
const size = encoded.length;
let bitpos = 0;
let bitbuf = 0;
let readPosition = 0;
const outLen = Math.floor((size * 5) / 8);
const out = new Uint8Array(outLen);
let outPos = 0;
while (readPosition < size || bitpos > 0) {
if (readPosition < size) {
const v = getValue(encoded[readPosition++]);
bitbuf = (bitbuf << 5) | v;
bitpos += 5;
}
while (bitpos >= 8) {
const d = (bitbuf >>> (bitpos - 8)) & 0xff;
out[outPos++] = d;
bitpos -= 8;
}
if (readPosition == size && bitpos > 0) {
bitbuf = (bitbuf << (8 - bitpos)) & 0xff;
bitpos = bitbuf == 0 ? 0 : 8;
}
}
return out;
}
export function eddsaGetPublic(eddsaPriv: Uint8Array): Uint8Array {
const pair = nacl.sign_keyPair_fromSeed(eddsaPriv);
return pair.publicKey;
}
export function ecdheGetPublic(ecdhePriv: Uint8Array): Uint8Array {
return nacl.scalarMult_base(ecdhePriv);
}
export function keyExchangeEddsaEcdhe(eddsaPriv: Uint8Array, ecdhePub: Uint8Array): Uint8Array {
const ph = nacl.hash(eddsaPriv);
const a = new Uint8Array(32);
for (let i = 0; i < 32; i++) {
a[i] = ph[i];
}
const x = nacl.scalarMult(a, ecdhePub);
return nacl.hash(x);
}
export function keyExchangeEcdheEddsa(ecdhePriv: Uint8Array, eddsaPub: Uint8Array): Uint8Array {
const curve25519Pub = nacl.sign_ed25519_pk_to_curve25519(eddsaPub);
const x = nacl.scalarMult(ecdhePriv, curve25519Pub);
return nacl.hash(x);
}
interface RsaPub {
N: bigint.BigInteger;
e: bigint.BigInteger;
}
interface RsaBlindingKey {
r: bigint.BigInteger;
}
/**
* KDF modulo a big integer.
*/
function kdfMod(
n: bigint.BigInteger,
ikm: Uint8Array,
salt: Uint8Array,
info: Uint8Array,
): bigint.BigInteger {
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 r;
}
counter++;
}
}
function stringToBuf(s: string) {
const te = new TextEncoder();
return te.encode(s);
}
function loadBigInt(arr: Uint8Array) {
return bigint.fromArray(Array.from(arr), 256, false);
}
function rsaBlindingKeyDerive(rsaPub: RsaPub, bks: Uint8Array): bigint.BigInteger {
const salt = stringToBuf("Blinding KDF extrator HMAC key");
const info = stringToBuf("Blinding KDF");
return kdfMod(rsaPub.N, bks, salt, info);
}
/*
* Test for malicious RSA key.
*
* Assuming n is an RSA modulous and r is generated using a call to
* GNUNET_CRYPTO_kdf_mod_mpi, if gcd(r,n) != 1 then n must be a
* malicious RSA key designed to deanomize the user.
*
* @param r KDF result
* @param n RSA modulus of the public key
*/
function rsaGcdValidate(r: bigint.BigInteger, n: bigint.BigInteger) {
const t = bigint.gcd(r, n);
if (!t.equals(bigint.one)) {
throw Error("malicious RSA public key");
}
}
function rsaFullDomainHash(hm: Uint8Array, rsaPub: RsaPub): bigint.BigInteger {
const info = stringToBuf("RSA-FDA FTpsW!");
const salt = rsaPubEncode(rsaPub);
const r = kdfMod(rsaPub.N, hm, salt, info);
rsaGcdValidate(r, rsaPub.N);
return r;
}
function rsaPubDecode(rsaPub: Uint8Array): RsaPub {
const modulusLength = (rsaPub[0] << 8) | rsaPub[1];
const exponentLength = (rsaPub[2] << 8) | rsaPub[3];
const modulus = rsaPub.slice(4, 4 + modulusLength)
const exponent = rsaPub.slice(4 + modulusLength, 4 + modulusLength + exponentLength);
const res = {
N: loadBigInt(modulus),
e: loadBigInt(exponent),
}
return res;
}
function rsaPubEncode(rsaPub: RsaPub): Uint8Array {
const mb = rsaPub.N.toArray(256).value;
const eb = rsaPub.e.toArray(256).value;
const out = new Uint8Array(4 + mb.length + eb.length);
out[0] = (mb.length >>> 8) & 0xFF;
out[1] = mb.length & 0xFF;
out[2] = (eb.length >>> 8) & 0xFF;
out[3] = eb.length & 0xFF;
out.set(mb, 4);
out.set(eb, 4 + mb.length);
return out;
}
export function rsaBlind(hm: Uint8Array, bks: Uint8Array, rsaPubEnc: Uint8Array): Uint8Array {
const rsaPub = rsaPubDecode(rsaPubEnc);
const data = rsaFullDomainHash(hm, rsaPub);
const r = rsaBlindingKeyDerive(rsaPub, bks);
const r_e = r.modPow(rsaPub.e, rsaPub.N);
const bm = r_e.multiply(data).mod(rsaPub.N);
return new Uint8Array(bm.toArray(256).value);
}
export function rsaUnblind(sig: Uint8Array, rsaPubEnc: Uint8Array, bks: Uint8Array): Uint8Array {
const rsaPub = rsaPubDecode(rsaPubEnc);
const blinded_s = loadBigInt(sig);
const r = rsaBlindingKeyDerive(rsaPub, bks);
const r_inv = r.modInv(rsaPub.N);
const s = blinded_s.multiply(r_inv).mod(rsaPub.N);
return new Uint8Array(s.toArray(256).value);
}
export function rsaVerify(hm: Uint8Array, rsaSig: Uint8Array, rsaPubEnc: Uint8Array): boolean {
const rsaPub = rsaPubDecode(rsaPubEnc);
const d = rsaFullDomainHash(hm, rsaPub);
const sig = loadBigInt(rsaSig);
const sig_e = sig.modPow(rsaPub.e, rsaPub.N);
return sig_e.equals(d);
}

View File

@ -329,6 +329,7 @@ export class CommandGroup<GN extends keyof any, TG> {
let foundSubcommand: CommandGroup<any, any> | undefined = undefined; let foundSubcommand: CommandGroup<any, any> | undefined = undefined;
const myArgs: any = (parsedArgs[this.argKey] = {}); const myArgs: any = (parsedArgs[this.argKey] = {});
const foundOptions: { [name: string]: boolean } = {}; const foundOptions: { [name: string]: boolean } = {};
const currentName = this.name ?? progname;
for (i = 0; i < unparsedArgs.length; i++) { for (i = 0; i < unparsedArgs.length; i++) {
const argVal = unparsedArgs[i]; const argVal = unparsedArgs[i];
if (argsTerminated == false) { if (argsTerminated == false) {
@ -341,8 +342,7 @@ export class CommandGroup<GN extends keyof any, TG> {
const r = splitOpt(opt); const r = splitOpt(opt);
const d = this.longOptions[r.key]; const d = this.longOptions[r.key];
if (!d) { if (!d) {
const n = this.name ?? progname; console.error(`error: unknown option '--${r.key}' for ${currentName}`);
console.error(`error: unknown option '--${r.key}' for ${n}`);
process.exit(-1); process.exit(-1);
throw Error("not reached"); throw Error("not reached");
} }
@ -412,8 +412,7 @@ export class CommandGroup<GN extends keyof any, TG> {
} else { } else {
const d = this.arguments[posArgIndex]; const d = this.arguments[posArgIndex];
if (!d) { if (!d) {
const n = this.name ?? progname; console.error(`error: too many arguments for ${currentName}`);
console.error(`error: too many arguments for ${n}`);
process.exit(-1); process.exit(-1);
throw Error("not reached"); throw Error("not reached");
} }
@ -424,12 +423,11 @@ export class CommandGroup<GN extends keyof any, TG> {
for (let i = posArgIndex; i < this.arguments.length; i++) { for (let i = posArgIndex; i < this.arguments.length; i++) {
const d = this.arguments[i]; const d = this.arguments[i];
const n = this.name ?? progname;
if (d.required) { if (d.required) {
if (d.args.default !== undefined) { if (d.args.default !== undefined) {
myArgs[d.name] = d.args.default; myArgs[d.name] = d.args.default;
} else { } else {
console.error(`error: missing positional argument '${d.name}' for ${n}`); console.error(`error: missing positional argument '${d.name}' for ${currentName}`);
process.exit(-1); process.exit(-1);
throw Error("not reached"); throw Error("not reached");
} }
@ -465,7 +463,19 @@ export class CommandGroup<GN extends keyof any, TG> {
parsedArgs, parsedArgs,
); );
} else if (this.myAction) { } else if (this.myAction) {
this.myAction(parsedArgs); let r;
try {
r = this.myAction(parsedArgs);
} catch (e) {
console.error(`An error occured while running ${currentName}`);
console.error(e);
process.exit(1);
}
Promise.resolve(r).catch((e) => {
console.error(`An error occured while running ${currentName}`);
console.error(e);
process.exit(1);
});
} else { } else {
this.printHelp(progname, parents); this.printHelp(progname, parents);
process.exit(-1); process.exit(-1);

View File

@ -82,8 +82,9 @@ export async function runIntegrationTest(args: {
throw Error("payment did not succeed"); throw Error("payment did not succeed");
} }
const refreshRes = await myWallet.refreshDirtyCoins(); await myWallet.runPending();
console.log(`waited to refresh ${refreshRes.numRefreshed} coins`); //const refreshRes = await myWallet.refreshDirtyCoins();
//console.log(`waited to refresh ${refreshRes.numRefreshed} coins`);
myWallet.stop(); myWallet.stop();
} }

View File

@ -15,6 +15,7 @@
*/ */
import os = require("os"); import os = require("os");
import fs = require("fs");
import { getDefaultNodeWallet, withdrawTestBalance } from "./helpers"; import { getDefaultNodeWallet, withdrawTestBalance } from "./helpers";
import { MerchantBackendConnection } from "./merchant"; import { MerchantBackendConnection } from "./merchant";
import { runIntegrationTest } from "./integrationtest"; import { runIntegrationTest } from "./integrationtest";
@ -24,6 +25,7 @@ import * as clk from "./clk";
import { BridgeIDBFactory, MemoryBackend } from "idb-bridge"; import { BridgeIDBFactory, MemoryBackend } from "idb-bridge";
import { Logger } from "../logging"; import { Logger } from "../logging";
import * as Amounts from "../amounts"; import * as Amounts from "../amounts";
import { decodeCrock } from "../crypto/talerCrypto";
const logger = new Logger("taler-wallet-cli.ts"); const logger = new Logger("taler-wallet-cli.ts");
@ -254,6 +256,16 @@ const advancedCli = walletCli.subcommand("advancedArgs", "advanced", {
"Subcommands for advanced operations (only use if you know what you're doing!).", "Subcommands for advanced operations (only use if you know what you're doing!).",
}); });
advancedCli
.subcommand("decode", "decode", {
help: "Decode base32-crockford",
})
.action(args => {
const enc = fs.readFileSync(0, 'utf8');
fs.writeFileSync(1, decodeCrock(enc.trim()))
});
advancedCli advancedCli
.subcommand("refresh", "force-refresh", { .subcommand("refresh", "force-refresh", {
help: "Force a refresh on a coin.", help: "Force a refresh on a coin.",

View File

@ -33,12 +33,15 @@
"src/crypto/cryptoWorker.ts", "src/crypto/cryptoWorker.ts",
"src/crypto/emscInterface-test.ts", "src/crypto/emscInterface-test.ts",
"src/crypto/emscInterface.ts", "src/crypto/emscInterface.ts",
"src/crypto/nativeCrypto-test.ts", "src/crypto/kdf.ts",
"src/crypto/nativeCrypto.ts", "src/crypto/nacl-fast.ts",
"src/crypto/nodeEmscriptenLoader.ts", "src/crypto/nodeEmscriptenLoader.ts",
"src/crypto/nodeProcessWorker.ts", "src/crypto/nodeProcessWorker.ts",
"src/crypto/nodeWorkerEntry.ts", "src/crypto/nodeWorkerEntry.ts",
"src/crypto/sha256.ts",
"src/crypto/synchronousWorker.ts", "src/crypto/synchronousWorker.ts",
"src/crypto/talerCrypto-test.ts",
"src/crypto/talerCrypto.ts",
"src/db.ts", "src/db.ts",
"src/dbTypes.ts", "src/dbTypes.ts",
"src/headless/bank.ts", "src/headless/bank.ts",

View File

@ -1120,6 +1120,11 @@ bfj@^6.1.1:
hoopy "^0.1.4" hoopy "^0.1.4"
tryer "^1.0.1" tryer "^1.0.1"
big-integer@^1.6.48:
version "1.6.48"
resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e"
integrity sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w==
big.js@^5.2.2: big.js@^5.2.2:
version "5.2.2" version "5.2.2"
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"