summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-core/src/crypto/cryptoImplementation.ts')
-rw-r--r--packages/taler-wallet-core/src/crypto/cryptoImplementation.ts258
1 files changed, 152 insertions, 106 deletions
diff --git a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
index d239270c8..0745d70c4 100644
--- a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
+++ b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
@@ -28,12 +28,14 @@ import {
AgeCommitmentProof,
AgeRestriction,
AmountJson,
- AmountLike,
Amounts,
AmountString,
+ amountToBuffer,
BlindedDenominationSignature,
bufferForUint32,
+ bufferForUint64,
buildSigPS,
+ canonicalJson,
CoinDepositPermission,
CoinEnvelope,
createHashContext,
@@ -42,7 +44,8 @@ import {
decryptContractForMerge,
DenomKeyType,
DepositInfo,
- ecdheGetPublic,
+ durationRoundedToBuffer,
+ ecdhGetPublic,
eddsaGetPublic,
EddsaPublicKeyString,
eddsaSign,
@@ -62,7 +65,7 @@ import {
hashTruncate32,
kdf,
kdfKw,
- keyExchangeEcdheEddsa,
+ keyExchangeEcdhEddsa,
Logger,
MakeSyncSignatureRequest,
PlanchetCreationRequest,
@@ -76,16 +79,15 @@ import {
rsaVerify,
setupTipPlanchet,
stringToBytes,
- TalerProtocolDuration,
TalerProtocolTimestamp,
TalerSignaturePurpose,
+ timestampRoundedToBuffer,
UnblindedSignature,
WireFee,
WithdrawalPlanchet,
} from "@gnu-taler/taler-util";
-import bigint from "big-integer";
// FIXME: Crypto should not use DB Types!
-import { DenominationRecord } from "../db.js";
+import { DenominationRecord, timestampProtocolFromDb } from "../db.js";
import {
CreateRecoupRefreshReqRequest,
CreateRecoupReqRequest,
@@ -101,9 +103,14 @@ import {
EncryptContractForDepositResponse,
EncryptContractRequest,
EncryptContractResponse,
- EncryptedContract,
+ SignCoinHistoryRequest,
+ SignCoinHistoryResponse,
+ SignDeletePurseRequest,
+ SignDeletePurseResponse,
SignPurseMergeRequest,
SignPurseMergeResponse,
+ SignRefundRequest,
+ SignRefundResponse,
SignReservePurseCreateRequest,
SignReservePurseCreateResponse,
SignTrackTransactionRequest,
@@ -159,7 +166,7 @@ export interface TalerCryptoInterface {
req: ContractTermsValidationRequest,
): Promise<ValidationResult>;
- createEddsaKeypair(req: unknown): Promise<EddsaKeypair>;
+ createEddsaKeypair(req: {}): Promise<EddsaKeypair>;
eddsaGetPublic(req: EddsaGetPublicRequest): Promise<EddsaGetPublicResponse>;
@@ -232,6 +239,16 @@ export interface TalerCryptoInterface {
signReservePurseCreate(
req: SignReservePurseCreateRequest,
): Promise<SignReservePurseCreateResponse>;
+
+ signRefund(req: SignRefundRequest): Promise<SignRefundResponse>;
+
+ signDeletePurse(
+ req: SignDeletePurseRequest,
+ ): Promise<SignDeletePurseResponse>;
+
+ signCoinHistoryRequest(
+ req: SignCoinHistoryRequest,
+ ): Promise<SignCoinHistoryResponse>;
}
/**
@@ -408,6 +425,19 @@ export const nullCrypto: TalerCryptoInterface = {
): Promise<SignReservePurseCreateResponse> {
throw new Error("Function not implemented.");
},
+ signRefund: function (req: SignRefundRequest): Promise<SignRefundResponse> {
+ throw new Error("Function not implemented.");
+ },
+ signDeletePurse: function (
+ req: SignDeletePurseRequest,
+ ): Promise<SignDeletePurseResponse> {
+ throw new Error("Function not implemented.");
+ },
+ signCoinHistoryRequest: function (
+ req: SignCoinHistoryRequest,
+ ): Promise<SignCoinHistoryResponse> {
+ throw new Error("Function not implemented.");
+ },
};
export type WithArg<X> = X extends (req: infer T) => infer R
@@ -445,17 +475,19 @@ export interface SignPurseCreationRequest {
minAge: number;
}
+export interface SpendCoinDetails {
+ coinPub: string;
+ coinPriv: string;
+ contribution: AmountString;
+ denomPubHash: string;
+ denomSig: UnblindedSignature;
+ ageCommitmentProof: AgeCommitmentProof | undefined;
+}
+
export interface SignPurseDepositsRequest {
pursePub: string;
exchangeBaseUrl: string;
- coins: {
- coinPub: string;
- coinPriv: string;
- contribution: AmountString;
- denomPubHash: string;
- denomSig: UnblindedSignature;
- ageCommitmentProof: AgeCommitmentProof | undefined;
- }[];
+ coins: SpendCoinDetails[];
}
export interface SignPurseDepositsResponse {
@@ -523,6 +555,9 @@ export interface WireAccountValidationRequest {
paytoUri: string;
sig: string;
masterPub: string;
+ conversionUrl?: string;
+ debitRestrictions?: any[];
+ creditRestrictions?: any[];
}
export interface EddsaKeypair {
@@ -664,7 +699,8 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
const salt = new Uint8Array(saltArrBuf);
const saltDataView = new DataView(saltArrBuf);
saltDataView.setUint32(0, req.coinNumber);
- const out = kdf(64, decodeCrock(req.secretSeed), salt, info);
+ const secretSeedDec = decodeCrock(req.secretSeed);
+ const out = kdf(64, secretSeedDec, salt, info);
const coinPriv = out.slice(0, 32);
const bks = out.slice(32, 64);
const coinPrivEnc = encodeCrock(coinPriv);
@@ -695,9 +731,10 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
if (denomPub.age_mask) {
const age = req.restrictAge || AgeRestriction.AGE_UNRESTRICTED;
logger.info(`creating age-restricted planchet (age ${age})`);
- maybeAcp = await AgeRestriction.restrictionCommit(
+ maybeAcp = await AgeRestriction.restrictionCommitSeeded(
denomPub.age_mask,
age,
+ stringToBytes(req.secretSeed),
);
maybeAgeCommitmentHash = AgeRestriction.hashCommitment(
maybeAcp.commitment,
@@ -799,7 +836,6 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
const p = buildSigPS(TalerSignaturePurpose.MERCHANT_TRACK_TRANSACTION)
.put(decodeCrock(req.contractTermsHash))
.put(decodeCrock(req.wireHash))
- .put(decodeCrock(req.merchantPub))
.put(decodeCrock(req.coinPub))
.build();
return { sig: encodeCrock(eddsaSign(p, decodeCrock(req.merchantPriv))) };
@@ -925,6 +961,7 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
const pub = decodeCrock(masterPub);
return { valid: eddsaVerify(p, sig, pub) };
},
+
/**
* Check if the signature of a denomination is valid.
*/
@@ -933,17 +970,25 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
req: DenominationValidationRequest,
): Promise<ValidationResult> {
const { masterPub, denom } = req;
- const value: AmountJson = {
- currency: denom.currency,
- fraction: denom.amountFrac,
- value: denom.amountVal,
- };
+ const value: AmountJson = Amounts.parseOrThrow(denom.value);
const p = buildSigPS(TalerSignaturePurpose.MASTER_DENOMINATION_KEY_VALIDITY)
.put(decodeCrock(masterPub))
- .put(timestampRoundedToBuffer(denom.stampStart))
- .put(timestampRoundedToBuffer(denom.stampExpireWithdraw))
- .put(timestampRoundedToBuffer(denom.stampExpireDeposit))
- .put(timestampRoundedToBuffer(denom.stampExpireLegal))
+ .put(timestampRoundedToBuffer(timestampProtocolFromDb(denom.stampStart)))
+ .put(
+ timestampRoundedToBuffer(
+ timestampProtocolFromDb(denom.stampExpireWithdraw),
+ ),
+ )
+ .put(
+ timestampRoundedToBuffer(
+ timestampProtocolFromDb(denom.stampExpireDeposit),
+ ),
+ )
+ .put(
+ timestampRoundedToBuffer(
+ timestampProtocolFromDb(denom.stampExpireLegal),
+ ),
+ )
.put(amountToBuffer(value))
.put(amountToBuffer(denom.fees.feeWithdraw))
.put(amountToBuffer(denom.fees.feeDeposit))
@@ -963,9 +1008,20 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
): Promise<ValidationResult> {
const { sig, masterPub, paytoUri } = req;
const paytoHash = hashTruncate32(stringToBytes(paytoUri + "\0"));
- const p = buildSigPS(TalerSignaturePurpose.MASTER_WIRE_DETAILS)
- .put(paytoHash)
- .build();
+ const pb = buildSigPS(TalerSignaturePurpose.MASTER_WIRE_DETAILS);
+ pb.put(paytoHash);
+ if (req.versionCurrent >= 15) {
+ let conversionUrlHash;
+ if (!req.conversionUrl) {
+ conversionUrlHash = new Uint8Array(64);
+ } else {
+ conversionUrlHash = hash(stringToBytes(req.conversionUrl + "\0"));
+ }
+ pb.put(conversionUrlHash);
+ pb.put(hash(stringToBytes(canonicalJson(req.debitRestrictions) + "\0")));
+ pb.put(hash(stringToBytes(canonicalJson(req.creditRestrictions) + "\0")));
+ }
+ const p = pb.build();
return { valid: eddsaVerify(p, decodeCrock(sig), decodeCrock(masterPub)) };
},
@@ -1078,7 +1134,7 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
depositInfo.ageCommitmentProof.commitment,
);
hAgeCommitment = decodeCrock(ach);
- if (depositInfo.requiredMinimumAge != null) {
+ if (depositInfo.requiredMinimumAge) {
minimumAgeSig = encodeCrock(
AgeRestriction.commitmentAttest(
depositInfo.ageCommitmentProof,
@@ -1090,6 +1146,8 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
// All zeros.
hAgeCommitment = new Uint8Array(32);
}
+ // FIXME: Actually allow passing user data here!
+ const walletDataHash = new Uint8Array(64);
let d: Uint8Array;
if (depositInfo.denomKeyType === DenomKeyType.Rsa) {
d = buildSigPS(TalerSignaturePurpose.WALLET_COIN_DEPOSIT)
@@ -1103,6 +1161,7 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
.put(amountToBuffer(depositInfo.spendAmount))
.put(amountToBuffer(depositInfo.feeDeposit))
.put(decodeCrock(depositInfo.merchantPub))
+ .put(walletDataHash)
.build();
} else {
throw Error("unsupported exchange protocol version");
@@ -1125,7 +1184,7 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
},
};
- if (depositInfo.requiredMinimumAge != null) {
+ if (depositInfo.requiredMinimumAge) {
// These are only required by the merchant
s.minimum_age_sig = minimumAgeSig;
s.age_commitment =
@@ -1355,7 +1414,7 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
): Promise<KeyExchangeResult> {
return {
h: encodeCrock(
- keyExchangeEcdheEddsa(
+ keyExchangeEcdhEddsa(
decodeCrock(req.ecdhePriv),
decodeCrock(req.eddsaPub),
),
@@ -1367,7 +1426,7 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
req: EcdheGetPublicRequest,
): Promise<EcdheGetPublicResponse> {
return {
- pub: encodeCrock(ecdheGetPublic(decodeCrock(req.priv))),
+ pub: encodeCrock(ecdhGetPublic(decodeCrock(req.priv))),
};
},
async setupRefreshTransferPub(
@@ -1409,15 +1468,12 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
const hExchangeBaseUrl = hash(stringToBytes(req.exchangeBaseUrl + "\0"));
const deposits: PurseDeposit[] = [];
for (const c of req.coins) {
- let haveAch: boolean;
let maybeAch: Uint8Array;
if (c.ageCommitmentProof) {
- haveAch = true;
maybeAch = decodeCrock(
AgeRestriction.hashCommitment(c.ageCommitmentProof.commitment),
);
} else {
- haveAch = false;
maybeAch = new Uint8Array(32);
}
const sigBlob = buildSigPS(TalerSignaturePurpose.WALLET_PURSE_DEPOSIT)
@@ -1450,25 +1506,24 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
tci: TalerCryptoInterfaceR,
req: EncryptContractRequest,
): Promise<EncryptContractResponse> {
- const contractKeyPair = await this.createEddsaKeypair(tci, {});
const enc = await encryptContractForMerge(
decodeCrock(req.pursePub),
- decodeCrock(contractKeyPair.priv),
+ decodeCrock(req.contractPriv),
decodeCrock(req.mergePriv),
req.contractTerms,
+ decodeCrock(req.nonce),
);
const sigBlob = buildSigPS(TalerSignaturePurpose.WALLET_PURSE_ECONTRACT)
.put(hash(enc))
- .put(decodeCrock(contractKeyPair.pub))
+ .put(decodeCrock(req.contractPub))
.build();
const sig = eddsaSign(sigBlob, decodeCrock(req.pursePriv));
return {
econtract: {
- contract_pub: contractKeyPair.pub,
+ contract_pub: req.contractPub,
econtract: encodeCrock(enc),
econtract_sig: encodeCrock(sig),
},
- contractPriv: contractKeyPair.priv,
};
},
async decryptContractForMerge(
@@ -1489,24 +1544,23 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
tci: TalerCryptoInterfaceR,
req: EncryptContractForDepositRequest,
): Promise<EncryptContractForDepositResponse> {
- const contractKeyPair = await this.createEddsaKeypair(tci, {});
const enc = await encryptContractForDeposit(
decodeCrock(req.pursePub),
- decodeCrock(contractKeyPair.priv),
+ decodeCrock(req.contractPriv),
req.contractTerms,
+ decodeCrock(req.nonce),
);
const sigBlob = buildSigPS(TalerSignaturePurpose.WALLET_PURSE_ECONTRACT)
.put(hash(enc))
- .put(decodeCrock(contractKeyPair.pub))
+ .put(decodeCrock(req.contractPub))
.build();
const sig = eddsaSign(sigBlob, decodeCrock(req.pursePriv));
return {
econtract: {
- contract_pub: contractKeyPair.pub,
+ contract_pub: req.contractPub,
econtract: encodeCrock(enc),
econtract_sig: encodeCrock(sig),
},
- contractPriv: contractKeyPair.priv,
};
},
async decryptContractForDeposit(
@@ -1626,66 +1680,58 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
purseSig: purseSigResp.sig,
};
},
+ async signRefund(
+ tci: TalerCryptoInterfaceR,
+ req: SignRefundRequest,
+ ): Promise<SignRefundResponse> {
+ const refundSigBlob = buildSigPS(TalerSignaturePurpose.MERCHANT_REFUND)
+ .put(decodeCrock(req.contractTermsHash))
+ .put(decodeCrock(req.coinPub))
+ .put(bufferForUint64(req.rtransactionId))
+ .put(amountToBuffer(req.refundAmount))
+ .build();
+ const refundSigResp = await tci.eddsaSign(tci, {
+ msg: encodeCrock(refundSigBlob),
+ priv: req.merchantPriv,
+ });
+ return {
+ sig: refundSigResp.sig,
+ };
+ },
+ async signDeletePurse(
+ tci: TalerCryptoInterfaceR,
+ req: SignDeletePurseRequest,
+ ): Promise<SignDeletePurseResponse> {
+ const deleteSigBlob = buildSigPS(
+ TalerSignaturePurpose.WALLET_PURSE_DELETE,
+ ).build();
+ const sigResp = await tci.eddsaSign(tci, {
+ msg: encodeCrock(deleteSigBlob),
+ priv: req.pursePriv,
+ });
+ return {
+ sig: sigResp.sig,
+ };
+ },
+ async signCoinHistoryRequest(
+ tci: TalerCryptoInterfaceR,
+ req: SignCoinHistoryRequest,
+ ): Promise<SignCoinHistoryResponse> {
+ const coinHistorySigBlob = buildSigPS(
+ TalerSignaturePurpose.WALLET_COIN_HISTORY,
+ )
+ .put(bufferForUint64(req.startOffset))
+ .build();
+ const sigResp = await tci.eddsaSign(tci, {
+ msg: encodeCrock(coinHistorySigBlob),
+ priv: req.coinPriv,
+ });
+ return {
+ sig: sigResp.sig,
+ };
+ },
};
-function amountToBuffer(amount: AmountLike): Uint8Array {
- const amountJ = Amounts.jsonifyAmount(amount);
- const buffer = new ArrayBuffer(8 + 4 + 12);
- const dvbuf = new DataView(buffer);
- const u8buf = new Uint8Array(buffer);
- const curr = stringToBytes(amountJ.currency);
- if (typeof dvbuf.setBigUint64 !== "undefined") {
- dvbuf.setBigUint64(0, BigInt(amountJ.value));
- } else {
- const arr = bigint(amountJ.value).toArray(2 ** 8).value;
- let offset = 8 - arr.length;
- for (let i = 0; i < arr.length; i++) {
- dvbuf.setUint8(offset++, arr[i]);
- }
- }
- dvbuf.setUint32(8, amountJ.fraction);
- u8buf.set(curr, 8 + 4);
-
- return u8buf;
-}
-
-function timestampRoundedToBuffer(ts: TalerProtocolTimestamp): Uint8Array {
- const b = new ArrayBuffer(8);
- const v = new DataView(b);
- // The buffer we sign over represents the timestamp in microseconds.
- if (typeof v.setBigUint64 !== "undefined") {
- const s = BigInt(ts.t_s) * BigInt(1000 * 1000);
- v.setBigUint64(0, s);
- } else {
- const s =
- ts.t_s === "never" ? bigint.zero : bigint(ts.t_s).multiply(1000 * 1000);
- const arr = s.toArray(2 ** 8).value;
- let offset = 8 - arr.length;
- for (let i = 0; i < arr.length; i++) {
- v.setUint8(offset++, arr[i]);
- }
- }
- return new Uint8Array(b);
-}
-
-function durationRoundedToBuffer(ts: TalerProtocolDuration): Uint8Array {
- const b = new ArrayBuffer(8);
- const v = new DataView(b);
- // The buffer we sign over represents the timestamp in microseconds.
- if (typeof v.setBigUint64 !== "undefined") {
- const s = BigInt(ts.d_us);
- v.setBigUint64(0, s);
- } else {
- const s = ts.d_us === "forever" ? bigint.zero : bigint(ts.d_us);
- const arr = s.toArray(2 ** 8).value;
- let offset = 8 - arr.length;
- for (let i = 0; i < arr.length; i++) {
- v.setUint8(offset++, arr[i]);
- }
- }
- return new Uint8Array(b);
-}
-
export interface EddsaSignRequest {
msg: string;
priv: string;