2019-08-15 23:34:08 +02:00
|
|
|
/*
|
|
|
|
This file is part of GNU Taler
|
2020-03-13 14:34:16 +01:00
|
|
|
(C) 2019-2020 Taler Systems SA
|
2019-08-15 23:34:08 +02:00
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
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
|
|
|
|
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
2021-11-16 17:20:36 +01:00
|
|
|
* Implementation of crypto-related high-level functions for the Taler wallet.
|
2020-03-13 14:34:16 +01:00
|
|
|
*
|
|
|
|
* @author Florian Dold <dold@taler.net>
|
2019-08-15 23:34:08 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Imports.
|
|
|
|
*/
|
|
|
|
|
2021-03-17 17:56:37 +01:00
|
|
|
// FIXME: Crypto should not use DB Types!
|
2021-10-18 21:48:22 +02:00
|
|
|
import {
|
2022-02-21 12:40:51 +01:00
|
|
|
AmountJson,
|
|
|
|
Amounts,
|
|
|
|
BenchmarkResult,
|
|
|
|
buildSigPS,
|
|
|
|
CoinDepositPermission,
|
|
|
|
CoinEnvelope,
|
|
|
|
createEddsaKeyPair,
|
|
|
|
createHashContext,
|
|
|
|
decodeCrock,
|
|
|
|
DenomKeyType,
|
|
|
|
DepositInfo,
|
|
|
|
eddsaGetPublic,
|
|
|
|
eddsaSign,
|
|
|
|
eddsaVerify,
|
|
|
|
encodeCrock,
|
|
|
|
ExchangeProtocolVersion,
|
|
|
|
hash,
|
|
|
|
hashCoinEv,
|
|
|
|
hashCoinEvInner,
|
|
|
|
hashDenomPub,
|
2022-03-07 12:09:38 +01:00
|
|
|
hashTruncate32,
|
2022-02-21 12:40:51 +01:00
|
|
|
keyExchangeEcdheEddsa,
|
|
|
|
Logger,
|
|
|
|
MakeSyncSignatureRequest,
|
|
|
|
PlanchetCreationRequest,
|
2022-03-14 18:31:30 +01:00
|
|
|
WithdrawalPlanchet,
|
2022-02-21 12:40:51 +01:00
|
|
|
RecoupRefreshRequest,
|
2021-10-18 21:48:22 +02:00
|
|
|
RecoupRequest,
|
2022-02-21 12:40:51 +01:00
|
|
|
RefreshPlanchetInfo,
|
|
|
|
rsaBlind,
|
|
|
|
rsaUnblind,
|
|
|
|
rsaVerify,
|
2020-12-14 16:44:42 +01:00
|
|
|
setupRefreshTransferPub,
|
2020-12-15 17:12:22 +01:00
|
|
|
setupTipPlanchet,
|
2022-02-21 12:40:51 +01:00
|
|
|
setupWithdrawPlanchet,
|
|
|
|
stringToBytes,
|
|
|
|
TalerSignaturePurpose,
|
2022-03-14 18:31:30 +01:00
|
|
|
BlindedDenominationSignature,
|
|
|
|
UnblindedSignature,
|
2022-03-15 17:51:05 +01:00
|
|
|
PlanchetUnblindInfo,
|
2022-03-18 15:32:41 +01:00
|
|
|
TalerProtocolTimestamp,
|
2022-03-24 01:10:34 +01:00
|
|
|
kdfKw,
|
|
|
|
bufferForUint32,
|
2021-10-07 12:01:40 +02:00
|
|
|
} from "@gnu-taler/taler-util";
|
2022-01-16 21:33:21 +01:00
|
|
|
import bigint from "big-integer";
|
2022-03-23 21:24:23 +01:00
|
|
|
import { DenominationRecord, WireFee } from "../db.js";
|
2020-12-14 16:44:42 +01:00
|
|
|
import {
|
2022-01-11 12:48:32 +01:00
|
|
|
CreateRecoupRefreshReqRequest,
|
|
|
|
CreateRecoupReqRequest,
|
2020-12-14 16:44:42 +01:00
|
|
|
DerivedRefreshSession,
|
2020-12-15 17:12:22 +01:00
|
|
|
DerivedTipPlanchet,
|
2020-12-14 16:44:42 +01:00
|
|
|
DeriveRefreshSessionRequest,
|
2020-12-15 17:12:22 +01:00
|
|
|
DeriveTipRequest,
|
2022-02-21 12:40:51 +01:00
|
|
|
SignTrackTransactionRequest,
|
2022-03-23 21:24:23 +01:00
|
|
|
} from "./cryptoTypes.js";
|
2020-12-03 14:15:40 +01:00
|
|
|
|
2022-03-23 21:24:23 +01:00
|
|
|
//const logger = new Logger("cryptoImplementation.ts");
|
2020-12-03 14:15:40 +01:00
|
|
|
|
2022-03-23 21:24:23 +01:00
|
|
|
/**
|
|
|
|
* Interface for (asynchronous) cryptographic operations that
|
|
|
|
* Taler uses.
|
|
|
|
*/
|
|
|
|
export interface TalerCryptoInterface {
|
|
|
|
/**
|
|
|
|
* Create a pre-coin of the given denomination to be withdrawn from then given
|
|
|
|
* reserve.
|
|
|
|
*/
|
|
|
|
createPlanchet(req: PlanchetCreationRequest): Promise<WithdrawalPlanchet>;
|
2019-11-28 00:46:34 +01:00
|
|
|
|
2022-03-23 21:24:23 +01:00
|
|
|
eddsaSign(req: EddsaSignRequest): Promise<EddsaSignResponse>;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a planchet used for tipping, including the private keys.
|
|
|
|
*/
|
|
|
|
createTipPlanchet(req: DeriveTipRequest): Promise<DerivedTipPlanchet>;
|
|
|
|
|
|
|
|
signTrackTransaction(
|
|
|
|
req: SignTrackTransactionRequest,
|
|
|
|
): Promise<EddsaSigningResult>;
|
|
|
|
|
|
|
|
createRecoupRequest(req: CreateRecoupReqRequest): Promise<RecoupRequest>;
|
|
|
|
|
|
|
|
createRecoupRefreshRequest(
|
|
|
|
req: CreateRecoupRefreshReqRequest,
|
|
|
|
): Promise<RecoupRefreshRequest>;
|
|
|
|
|
|
|
|
isValidPaymentSignature(
|
|
|
|
req: PaymentSignatureValidationRequest,
|
|
|
|
): Promise<ValidationResult>;
|
|
|
|
|
|
|
|
isValidWireFee(req: WireFeeValidationRequest): Promise<ValidationResult>;
|
|
|
|
|
|
|
|
isValidDenom(req: DenominationValidationRequest): Promise<ValidationResult>;
|
|
|
|
|
|
|
|
isValidWireAccount(
|
|
|
|
req: WireAccountValidationRequest,
|
|
|
|
): Promise<ValidationResult>;
|
|
|
|
|
|
|
|
isValidContractTermsSignature(
|
|
|
|
req: ContractTermsValidationRequest,
|
|
|
|
): Promise<ValidationResult>;
|
|
|
|
|
|
|
|
createEddsaKeypair(req: {}): Promise<EddsaKeypair>;
|
|
|
|
|
|
|
|
eddsaGetPublic(req: EddsaGetPublicRequest): Promise<EddsaKeypair>;
|
|
|
|
|
|
|
|
unblindDenominationSignature(
|
|
|
|
req: UnblindDenominationSignatureRequest,
|
|
|
|
): Promise<UnblindedSignature>;
|
|
|
|
|
|
|
|
rsaUnblind(req: RsaUnblindRequest): Promise<RsaUnblindResponse>;
|
|
|
|
|
|
|
|
rsaVerify(req: RsaVerificationRequest): Promise<ValidationResult>;
|
|
|
|
|
2022-03-24 01:10:34 +01:00
|
|
|
rsaBlind(req: RsaBlindRequest): Promise<RsaBlindResponse>;
|
|
|
|
|
2022-03-23 21:24:23 +01:00
|
|
|
signDepositPermission(
|
|
|
|
depositInfo: DepositInfo,
|
|
|
|
): Promise<CoinDepositPermission>;
|
|
|
|
|
|
|
|
deriveRefreshSession(
|
|
|
|
req: DeriveRefreshSessionRequest,
|
|
|
|
): Promise<DerivedRefreshSession>;
|
|
|
|
|
|
|
|
hashString(req: HashStringRequest): Promise<HashStringResult>;
|
|
|
|
|
|
|
|
signCoinLink(req: SignCoinLinkRequest): Promise<EddsaSigningResult>;
|
|
|
|
|
|
|
|
makeSyncSignature(req: MakeSyncSignatureRequest): Promise<EddsaSigningResult>;
|
2022-03-24 01:10:34 +01:00
|
|
|
|
|
|
|
setupRefreshPlanchet(
|
|
|
|
req: SetupRefreshPlanchetRequest,
|
|
|
|
): Promise<FreshCoinEncoded>;
|
|
|
|
|
|
|
|
keyExchangeEcdheEddsa(
|
|
|
|
req: KeyExchangeEcdheEddsaRequest,
|
|
|
|
): Promise<KeyExchangeResult>;
|
2019-11-28 00:46:34 +01:00
|
|
|
}
|
|
|
|
|
2022-03-23 21:24:23 +01:00
|
|
|
/**
|
|
|
|
* Implementation of the Taler crypto interface where every function
|
|
|
|
* always throws. Only useful in practice as a way to iterate through
|
|
|
|
* all possible crypto functions.
|
|
|
|
*
|
|
|
|
* (This list can be easily auto-generated by your favorite IDE).
|
|
|
|
*/
|
|
|
|
export const nullCrypto: TalerCryptoInterface = {
|
|
|
|
createPlanchet: function (
|
|
|
|
req: PlanchetCreationRequest,
|
|
|
|
): Promise<WithdrawalPlanchet> {
|
|
|
|
throw new Error("Function not implemented.");
|
|
|
|
},
|
|
|
|
eddsaSign: function (req: EddsaSignRequest): Promise<EddsaSignResponse> {
|
|
|
|
throw new Error("Function not implemented.");
|
|
|
|
},
|
|
|
|
createTipPlanchet: function (
|
|
|
|
req: DeriveTipRequest,
|
|
|
|
): Promise<DerivedTipPlanchet> {
|
|
|
|
throw new Error("Function not implemented.");
|
|
|
|
},
|
|
|
|
signTrackTransaction: function (
|
|
|
|
req: SignTrackTransactionRequest,
|
|
|
|
): Promise<EddsaSigningResult> {
|
|
|
|
throw new Error("Function not implemented.");
|
|
|
|
},
|
|
|
|
createRecoupRequest: function (
|
|
|
|
req: CreateRecoupReqRequest,
|
|
|
|
): Promise<RecoupRequest> {
|
|
|
|
throw new Error("Function not implemented.");
|
|
|
|
},
|
|
|
|
createRecoupRefreshRequest: function (
|
|
|
|
req: CreateRecoupRefreshReqRequest,
|
|
|
|
): Promise<RecoupRefreshRequest> {
|
|
|
|
throw new Error("Function not implemented.");
|
|
|
|
},
|
|
|
|
isValidPaymentSignature: function (
|
|
|
|
req: PaymentSignatureValidationRequest,
|
|
|
|
): Promise<ValidationResult> {
|
|
|
|
throw new Error("Function not implemented.");
|
|
|
|
},
|
|
|
|
isValidWireFee: function (
|
|
|
|
req: WireFeeValidationRequest,
|
|
|
|
): Promise<ValidationResult> {
|
|
|
|
throw new Error("Function not implemented.");
|
|
|
|
},
|
|
|
|
isValidDenom: function (
|
|
|
|
req: DenominationValidationRequest,
|
|
|
|
): Promise<ValidationResult> {
|
|
|
|
throw new Error("Function not implemented.");
|
|
|
|
},
|
|
|
|
isValidWireAccount: function (
|
|
|
|
req: WireAccountValidationRequest,
|
|
|
|
): Promise<ValidationResult> {
|
|
|
|
throw new Error("Function not implemented.");
|
|
|
|
},
|
|
|
|
isValidContractTermsSignature: function (
|
|
|
|
req: ContractTermsValidationRequest,
|
|
|
|
): Promise<ValidationResult> {
|
|
|
|
throw new Error("Function not implemented.");
|
|
|
|
},
|
|
|
|
createEddsaKeypair: function (req: {}): Promise<EddsaKeypair> {
|
|
|
|
throw new Error("Function not implemented.");
|
|
|
|
},
|
|
|
|
eddsaGetPublic: function (req: EddsaGetPublicRequest): Promise<EddsaKeypair> {
|
|
|
|
throw new Error("Function not implemented.");
|
|
|
|
},
|
|
|
|
unblindDenominationSignature: function (
|
|
|
|
req: UnblindDenominationSignatureRequest,
|
|
|
|
): Promise<UnblindedSignature> {
|
|
|
|
throw new Error("Function not implemented.");
|
|
|
|
},
|
|
|
|
rsaUnblind: function (req: RsaUnblindRequest): Promise<RsaUnblindResponse> {
|
|
|
|
throw new Error("Function not implemented.");
|
|
|
|
},
|
|
|
|
rsaVerify: function (req: RsaVerificationRequest): Promise<ValidationResult> {
|
|
|
|
throw new Error("Function not implemented.");
|
|
|
|
},
|
|
|
|
signDepositPermission: function (
|
|
|
|
depositInfo: DepositInfo,
|
|
|
|
): Promise<CoinDepositPermission> {
|
|
|
|
throw new Error("Function not implemented.");
|
|
|
|
},
|
|
|
|
deriveRefreshSession: function (
|
|
|
|
req: DeriveRefreshSessionRequest,
|
|
|
|
): Promise<DerivedRefreshSession> {
|
|
|
|
throw new Error("Function not implemented.");
|
|
|
|
},
|
|
|
|
hashString: function (req: HashStringRequest): Promise<HashStringResult> {
|
|
|
|
throw new Error("Function not implemented.");
|
|
|
|
},
|
|
|
|
signCoinLink: function (
|
|
|
|
req: SignCoinLinkRequest,
|
|
|
|
): Promise<EddsaSigningResult> {
|
|
|
|
throw new Error("Function not implemented.");
|
|
|
|
},
|
|
|
|
makeSyncSignature: function (
|
|
|
|
req: MakeSyncSignatureRequest,
|
|
|
|
): Promise<EddsaSigningResult> {
|
|
|
|
throw new Error("Function not implemented.");
|
|
|
|
},
|
2022-03-24 01:10:34 +01:00
|
|
|
setupRefreshPlanchet: function (
|
|
|
|
req: SetupRefreshPlanchetRequest,
|
|
|
|
): Promise<FreshCoinEncoded> {
|
|
|
|
throw new Error("Function not implemented.");
|
|
|
|
},
|
|
|
|
rsaBlind: function (req: RsaBlindRequest): Promise<RsaBlindResponse> {
|
|
|
|
throw new Error("Function not implemented.");
|
|
|
|
},
|
|
|
|
keyExchangeEcdheEddsa: function (
|
|
|
|
req: KeyExchangeEcdheEddsaRequest,
|
|
|
|
): Promise<KeyExchangeResult> {
|
|
|
|
throw new Error("Function not implemented.");
|
|
|
|
},
|
2022-03-23 21:24:23 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
export type WithArg<X> = X extends (req: infer T) => infer R
|
|
|
|
? (tci: TalerCryptoInterfaceR, req: T) => R
|
|
|
|
: never;
|
|
|
|
|
|
|
|
export type TalerCryptoInterfaceR = {
|
|
|
|
[x in keyof TalerCryptoInterface]: WithArg<TalerCryptoInterface[x]>;
|
|
|
|
};
|
|
|
|
|
|
|
|
export interface SignCoinLinkRequest {
|
|
|
|
oldCoinPriv: string;
|
|
|
|
newDenomHash: string;
|
|
|
|
oldCoinPub: string;
|
|
|
|
transferPub: string;
|
|
|
|
coinEv: CoinEnvelope;
|
2019-11-28 00:46:34 +01:00
|
|
|
}
|
2020-04-07 10:42:29 +02:00
|
|
|
|
2022-03-24 01:10:34 +01:00
|
|
|
export interface SetupRefreshPlanchetRequest {
|
|
|
|
transferSecret: string;
|
|
|
|
coinNumber: number;
|
|
|
|
}
|
|
|
|
|
2022-03-23 21:24:23 +01:00
|
|
|
export interface RsaVerificationRequest {
|
|
|
|
hm: string;
|
|
|
|
sig: string;
|
|
|
|
pk: string;
|
2021-12-09 10:39:50 +01:00
|
|
|
}
|
|
|
|
|
2022-03-24 01:10:34 +01:00
|
|
|
export interface RsaBlindRequest {
|
|
|
|
hm: string;
|
|
|
|
bks: string;
|
|
|
|
pub: string;
|
|
|
|
}
|
|
|
|
|
2022-03-23 21:24:23 +01:00
|
|
|
export interface EddsaSigningResult {
|
|
|
|
sig: string;
|
2021-11-16 17:20:36 +01:00
|
|
|
}
|
|
|
|
|
2022-03-23 21:24:23 +01:00
|
|
|
export interface ValidationResult {
|
|
|
|
valid: boolean;
|
|
|
|
}
|
2019-08-15 23:34:08 +02:00
|
|
|
|
2022-03-23 21:24:23 +01:00
|
|
|
export interface HashStringRequest {
|
|
|
|
str: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface HashStringResult {
|
|
|
|
h: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface WireFeeValidationRequest {
|
|
|
|
type: string;
|
|
|
|
wf: WireFee;
|
|
|
|
masterPub: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface DenominationValidationRequest {
|
|
|
|
denom: DenominationRecord;
|
|
|
|
masterPub: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface PaymentSignatureValidationRequest {
|
|
|
|
sig: string;
|
|
|
|
contractHash: string;
|
|
|
|
merchantPub: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface ContractTermsValidationRequest {
|
|
|
|
contractTermsHash: string;
|
|
|
|
sig: string;
|
|
|
|
merchantPub: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface WireAccountValidationRequest {
|
|
|
|
versionCurrent: ExchangeProtocolVersion;
|
|
|
|
paytoUri: string;
|
|
|
|
sig: string;
|
|
|
|
masterPub: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface EddsaKeypair {
|
|
|
|
priv: string;
|
|
|
|
pub: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface EddsaGetPublicRequest {
|
|
|
|
priv: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface UnblindDenominationSignatureRequest {
|
|
|
|
planchet: PlanchetUnblindInfo;
|
|
|
|
evSig: BlindedDenominationSignature;
|
|
|
|
}
|
|
|
|
|
2022-03-24 01:10:34 +01:00
|
|
|
export interface FreshCoinEncoded {
|
|
|
|
coinPub: string;
|
|
|
|
coinPriv: string;
|
|
|
|
bks: string;
|
|
|
|
}
|
|
|
|
|
2022-03-23 21:24:23 +01:00
|
|
|
export interface RsaUnblindRequest {
|
|
|
|
blindedSig: string;
|
|
|
|
bk: string;
|
|
|
|
pk: string;
|
|
|
|
}
|
|
|
|
|
2022-03-24 01:10:34 +01:00
|
|
|
export interface RsaBlindResponse {
|
|
|
|
blinded: string;
|
|
|
|
}
|
|
|
|
|
2022-03-23 21:24:23 +01:00
|
|
|
export interface RsaUnblindResponse {
|
|
|
|
sig: string;
|
|
|
|
}
|
|
|
|
|
2022-03-24 01:10:34 +01:00
|
|
|
export interface KeyExchangeEcdheEddsaRequest {
|
|
|
|
ecdhePriv: string;
|
|
|
|
eddsaPub: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface KeyExchangeResult {
|
|
|
|
h: string;
|
|
|
|
}
|
|
|
|
|
2022-03-23 21:24:23 +01:00
|
|
|
export const nativeCryptoR: TalerCryptoInterfaceR = {
|
|
|
|
async eddsaSign(
|
|
|
|
tci: TalerCryptoInterfaceR,
|
|
|
|
req: EddsaSignRequest,
|
|
|
|
): Promise<EddsaSignResponse> {
|
|
|
|
return {
|
|
|
|
sig: encodeCrock(eddsaSign(decodeCrock(req.msg), decodeCrock(req.priv))),
|
|
|
|
};
|
|
|
|
},
|
2021-11-16 17:20:36 +01:00
|
|
|
|
2022-03-24 01:10:34 +01:00
|
|
|
async rsaBlind(
|
|
|
|
tci: TalerCryptoInterfaceR,
|
|
|
|
req: RsaBlindRequest,
|
|
|
|
): Promise<RsaBlindResponse> {
|
|
|
|
const res = rsaBlind(
|
|
|
|
decodeCrock(req.hm),
|
|
|
|
decodeCrock(req.bks),
|
|
|
|
decodeCrock(req.pub),
|
|
|
|
);
|
|
|
|
return {
|
|
|
|
blinded: encodeCrock(res),
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
async setupRefreshPlanchet(
|
|
|
|
tci: TalerCryptoInterfaceR,
|
|
|
|
req: SetupRefreshPlanchetRequest,
|
|
|
|
): Promise<FreshCoinEncoded> {
|
|
|
|
const transferSecret = decodeCrock(req.transferSecret);
|
|
|
|
const coinNumber = req.coinNumber;
|
|
|
|
// See TALER_transfer_secret_to_planchet_secret in C impl
|
|
|
|
const planchetMasterSecret = kdfKw({
|
|
|
|
ikm: transferSecret,
|
|
|
|
outputLength: 32,
|
|
|
|
salt: bufferForUint32(coinNumber),
|
|
|
|
info: stringToBytes("taler-coin-derivation"),
|
|
|
|
});
|
|
|
|
|
|
|
|
const coinPriv = kdfKw({
|
|
|
|
ikm: planchetMasterSecret,
|
|
|
|
outputLength: 32,
|
|
|
|
salt: stringToBytes("coin"),
|
|
|
|
});
|
|
|
|
|
|
|
|
const bks = kdfKw({
|
|
|
|
ikm: planchetMasterSecret,
|
|
|
|
outputLength: 32,
|
|
|
|
salt: stringToBytes("bks"),
|
|
|
|
});
|
|
|
|
|
|
|
|
return {
|
|
|
|
bks: encodeCrock(bks),
|
|
|
|
coinPriv: encodeCrock(coinPriv),
|
|
|
|
coinPub: encodeCrock(eddsaGetPublic(coinPriv)),
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
2021-12-09 10:39:50 +01:00
|
|
|
async createPlanchet(
|
2022-03-23 21:24:23 +01:00
|
|
|
tci: TalerCryptoInterfaceR,
|
2021-12-09 10:39:50 +01:00
|
|
|
req: PlanchetCreationRequest,
|
2022-03-14 18:31:30 +01:00
|
|
|
): Promise<WithdrawalPlanchet> {
|
2022-02-21 12:40:51 +01:00
|
|
|
const denomPub = req.denomPub;
|
|
|
|
if (denomPub.cipher === DenomKeyType.Rsa) {
|
2021-11-27 20:56:58 +01:00
|
|
|
const reservePub = decodeCrock(req.reservePub);
|
2022-02-21 12:40:51 +01:00
|
|
|
const denomPubRsa = decodeCrock(denomPub.rsa_public_key);
|
2021-11-27 20:56:58 +01:00
|
|
|
const derivedPlanchet = setupWithdrawPlanchet(
|
|
|
|
decodeCrock(req.secretSeed),
|
|
|
|
req.coinIndex,
|
|
|
|
);
|
|
|
|
const coinPubHash = hash(derivedPlanchet.coinPub);
|
2022-03-24 01:10:34 +01:00
|
|
|
const blindResp = await tci.rsaBlind(tci, {
|
|
|
|
bks: encodeCrock(derivedPlanchet.bks),
|
|
|
|
hm: encodeCrock(coinPubHash),
|
|
|
|
pub: denomPub.rsa_public_key,
|
|
|
|
});
|
2022-02-21 12:40:51 +01:00
|
|
|
const coinEv: CoinEnvelope = {
|
|
|
|
cipher: DenomKeyType.Rsa,
|
2022-03-24 01:10:34 +01:00
|
|
|
rsa_blinded_planchet: blindResp.blinded,
|
2022-02-21 12:40:51 +01:00
|
|
|
};
|
2021-11-27 20:56:58 +01:00
|
|
|
const amountWithFee = Amounts.add(req.value, req.feeWithdraw).amount;
|
|
|
|
const denomPubHash = hashDenomPub(req.denomPub);
|
2022-02-21 12:40:51 +01:00
|
|
|
const evHash = hashCoinEv(coinEv, encodeCrock(denomPubHash));
|
2021-11-27 20:56:58 +01:00
|
|
|
const withdrawRequest = buildSigPS(
|
|
|
|
TalerSignaturePurpose.WALLET_RESERVE_WITHDRAW,
|
|
|
|
)
|
|
|
|
.put(amountToBuffer(amountWithFee))
|
|
|
|
.put(denomPubHash)
|
|
|
|
.put(evHash)
|
|
|
|
.build();
|
|
|
|
|
2022-03-23 21:24:23 +01:00
|
|
|
const sigResult = await tci.eddsaSign(tci, {
|
2021-12-09 10:39:50 +01:00
|
|
|
msg: encodeCrock(withdrawRequest),
|
|
|
|
priv: req.reservePriv,
|
|
|
|
});
|
2021-11-27 20:56:58 +01:00
|
|
|
|
2022-03-14 18:31:30 +01:00
|
|
|
const planchet: WithdrawalPlanchet = {
|
2021-11-27 20:56:58 +01:00
|
|
|
blindingKey: encodeCrock(derivedPlanchet.bks),
|
2022-02-21 12:40:51 +01:00
|
|
|
coinEv,
|
2021-11-27 20:56:58 +01:00
|
|
|
coinPriv: encodeCrock(derivedPlanchet.coinPriv),
|
|
|
|
coinPub: encodeCrock(derivedPlanchet.coinPub),
|
|
|
|
coinValue: req.value,
|
2022-02-21 12:40:51 +01:00
|
|
|
denomPub,
|
2021-11-27 20:56:58 +01:00
|
|
|
denomPubHash: encodeCrock(denomPubHash),
|
|
|
|
reservePub: encodeCrock(reservePub),
|
2021-12-09 10:39:50 +01:00
|
|
|
withdrawSig: sigResult.sig,
|
2021-11-27 20:56:58 +01:00
|
|
|
coinEvHash: encodeCrock(evHash),
|
|
|
|
};
|
|
|
|
return planchet;
|
|
|
|
} else {
|
|
|
|
throw Error("unsupported cipher, unable to create planchet");
|
2021-11-17 10:23:22 +01:00
|
|
|
}
|
2022-03-23 21:24:23 +01:00
|
|
|
},
|
2019-08-15 23:34:08 +02:00
|
|
|
|
2022-03-23 21:24:23 +01:00
|
|
|
async createTipPlanchet(
|
|
|
|
tci: TalerCryptoInterfaceR,
|
|
|
|
req: DeriveTipRequest,
|
|
|
|
): Promise<DerivedTipPlanchet> {
|
2022-02-21 12:40:51 +01:00
|
|
|
if (req.denomPub.cipher !== DenomKeyType.Rsa) {
|
|
|
|
throw Error(`unsupported cipher (${req.denomPub.cipher})`);
|
2021-11-17 10:23:22 +01:00
|
|
|
}
|
2020-12-15 17:12:22 +01:00
|
|
|
const fc = setupTipPlanchet(decodeCrock(req.secretSeed), req.planchetIndex);
|
2021-11-17 10:23:22 +01:00
|
|
|
const denomPub = decodeCrock(req.denomPub.rsa_public_key);
|
2020-12-15 17:12:22 +01:00
|
|
|
const coinPubHash = hash(fc.coinPub);
|
2022-03-24 01:10:34 +01:00
|
|
|
const blindResp = await tci.rsaBlind(tci, {
|
|
|
|
bks: encodeCrock(fc.bks),
|
|
|
|
hm: encodeCrock(coinPubHash),
|
|
|
|
pub: encodeCrock(denomPub),
|
|
|
|
});
|
2022-03-07 20:44:18 +01:00
|
|
|
const coinEv = {
|
|
|
|
cipher: DenomKeyType.Rsa,
|
2022-03-24 01:10:34 +01:00
|
|
|
rsa_blinded_planchet: blindResp.blinded,
|
2022-03-07 20:44:18 +01:00
|
|
|
};
|
2020-12-15 17:12:22 +01:00
|
|
|
const tipPlanchet: DerivedTipPlanchet = {
|
2021-01-06 17:06:19 +01:00
|
|
|
blindingKey: encodeCrock(fc.bks),
|
2022-03-07 20:44:18 +01:00
|
|
|
coinEv,
|
|
|
|
coinEvHash: encodeCrock(
|
|
|
|
hashCoinEv(coinEv, encodeCrock(hashDenomPub(req.denomPub))),
|
|
|
|
),
|
2020-12-15 17:12:22 +01:00
|
|
|
coinPriv: encodeCrock(fc.coinPriv),
|
|
|
|
coinPub: encodeCrock(fc.coinPub),
|
2019-08-15 23:34:08 +02:00
|
|
|
};
|
|
|
|
return tipPlanchet;
|
2022-03-23 21:24:23 +01:00
|
|
|
},
|
2019-08-15 23:34:08 +02:00
|
|
|
|
2022-03-23 21:24:23 +01:00
|
|
|
async signTrackTransaction(
|
|
|
|
tci: TalerCryptoInterfaceR,
|
|
|
|
req: SignTrackTransactionRequest,
|
|
|
|
): Promise<EddsaSigningResult> {
|
2021-10-18 21:48:22 +02:00
|
|
|
const p = buildSigPS(TalerSignaturePurpose.MERCHANT_TRACK_TRANSACTION)
|
2021-01-18 23:35:41 +01:00
|
|
|
.put(decodeCrock(req.contractTermsHash))
|
|
|
|
.put(decodeCrock(req.wireHash))
|
|
|
|
.put(decodeCrock(req.merchantPub))
|
|
|
|
.put(decodeCrock(req.coinPub))
|
|
|
|
.build();
|
2022-03-23 21:24:23 +01:00
|
|
|
return { sig: encodeCrock(eddsaSign(p, decodeCrock(req.merchantPriv))) };
|
|
|
|
},
|
2021-01-18 23:35:41 +01:00
|
|
|
|
2019-08-15 23:34:08 +02:00
|
|
|
/**
|
2020-03-11 20:14:28 +01:00
|
|
|
* Create and sign a message to recoup a coin.
|
2019-08-15 23:34:08 +02:00
|
|
|
*/
|
2022-03-23 21:24:23 +01:00
|
|
|
async createRecoupRequest(
|
|
|
|
tci: TalerCryptoInterfaceR,
|
|
|
|
req: CreateRecoupReqRequest,
|
|
|
|
): Promise<RecoupRequest> {
|
2021-10-18 21:48:22 +02:00
|
|
|
const p = buildSigPS(TalerSignaturePurpose.WALLET_COIN_RECOUP)
|
2022-01-11 12:48:32 +01:00
|
|
|
.put(decodeCrock(req.denomPubHash))
|
|
|
|
.put(decodeCrock(req.blindingKey))
|
2019-11-28 00:46:34 +01:00
|
|
|
.build();
|
|
|
|
|
2022-01-11 12:48:32 +01:00
|
|
|
const coinPriv = decodeCrock(req.coinPriv);
|
2019-11-28 00:46:34 +01:00
|
|
|
const coinSig = eddsaSign(p, coinPriv);
|
2022-02-21 12:40:51 +01:00
|
|
|
if (req.denomPub.cipher === DenomKeyType.Rsa) {
|
2021-11-27 20:56:58 +01:00
|
|
|
const paybackRequest: RecoupRequest = {
|
2022-01-11 12:48:32 +01:00
|
|
|
coin_blind_key_secret: req.blindingKey,
|
|
|
|
coin_sig: encodeCrock(coinSig),
|
|
|
|
denom_pub_hash: req.denomPubHash,
|
|
|
|
denom_sig: req.denomSig,
|
2022-03-07 21:49:11 +01:00
|
|
|
// FIXME!
|
|
|
|
ewv: {
|
|
|
|
cipher: "RSA",
|
|
|
|
},
|
2022-01-11 12:48:32 +01:00
|
|
|
};
|
|
|
|
return paybackRequest;
|
2022-02-21 12:40:51 +01:00
|
|
|
} else {
|
|
|
|
throw new Error();
|
2022-01-11 12:48:32 +01:00
|
|
|
}
|
2022-03-23 21:24:23 +01:00
|
|
|
},
|
2022-01-11 12:48:32 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Create and sign a message to recoup a coin.
|
|
|
|
*/
|
2022-03-23 21:24:23 +01:00
|
|
|
async createRecoupRefreshRequest(
|
|
|
|
tci: TalerCryptoInterfaceR,
|
2022-01-12 15:51:56 +01:00
|
|
|
req: CreateRecoupRefreshReqRequest,
|
2022-03-23 21:24:23 +01:00
|
|
|
): Promise<RecoupRefreshRequest> {
|
2022-01-11 12:48:32 +01:00
|
|
|
const p = buildSigPS(TalerSignaturePurpose.WALLET_COIN_RECOUP_REFRESH)
|
|
|
|
.put(decodeCrock(req.denomPubHash))
|
|
|
|
.put(decodeCrock(req.blindingKey))
|
|
|
|
.build();
|
|
|
|
|
|
|
|
const coinPriv = decodeCrock(req.coinPriv);
|
|
|
|
const coinSig = eddsaSign(p, coinPriv);
|
2022-02-21 12:40:51 +01:00
|
|
|
if (req.denomPub.cipher === DenomKeyType.Rsa) {
|
2022-01-12 15:51:56 +01:00
|
|
|
const recoupRequest: RecoupRefreshRequest = {
|
2022-01-11 12:48:32 +01:00
|
|
|
coin_blind_key_secret: req.blindingKey,
|
2021-11-27 20:56:58 +01:00
|
|
|
coin_sig: encodeCrock(coinSig),
|
2022-01-11 12:48:32 +01:00
|
|
|
denom_pub_hash: req.denomPubHash,
|
|
|
|
denom_sig: req.denomSig,
|
2022-03-07 21:49:11 +01:00
|
|
|
// FIXME!
|
|
|
|
ewv: {
|
|
|
|
cipher: "RSA",
|
|
|
|
},
|
2021-11-27 20:56:58 +01:00
|
|
|
};
|
2022-01-12 15:51:56 +01:00
|
|
|
return recoupRequest;
|
2022-02-21 12:40:51 +01:00
|
|
|
} else {
|
|
|
|
throw new Error();
|
2021-11-27 20:56:58 +01:00
|
|
|
}
|
2022-03-23 21:24:23 +01:00
|
|
|
},
|
2019-08-15 23:34:08 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if a payment signature is valid.
|
|
|
|
*/
|
2022-03-23 21:24:23 +01:00
|
|
|
async isValidPaymentSignature(
|
|
|
|
tci: TalerCryptoInterfaceR,
|
|
|
|
req: PaymentSignatureValidationRequest,
|
|
|
|
): Promise<ValidationResult> {
|
|
|
|
const { contractHash, sig, merchantPub } = req;
|
2021-10-18 21:48:22 +02:00
|
|
|
const p = buildSigPS(TalerSignaturePurpose.MERCHANT_PAYMENT_OK)
|
2019-11-28 00:46:34 +01:00
|
|
|
.put(decodeCrock(contractHash))
|
|
|
|
.build();
|
|
|
|
const sigBytes = decodeCrock(sig);
|
|
|
|
const pubBytes = decodeCrock(merchantPub);
|
2022-03-23 21:24:23 +01:00
|
|
|
return { valid: eddsaVerify(p, sigBytes, pubBytes) };
|
|
|
|
},
|
2019-08-15 23:34:08 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if a wire fee is correctly signed.
|
|
|
|
*/
|
2021-11-16 17:20:36 +01:00
|
|
|
async isValidWireFee(
|
2022-03-23 21:24:23 +01:00
|
|
|
tci: TalerCryptoInterfaceR,
|
|
|
|
req: WireFeeValidationRequest,
|
|
|
|
): Promise<ValidationResult> {
|
|
|
|
const { type, wf, masterPub } = req;
|
2021-10-18 21:48:22 +02:00
|
|
|
const p = buildSigPS(TalerSignaturePurpose.MASTER_WIRE_FEES)
|
2019-11-28 00:46:34 +01:00
|
|
|
.put(hash(stringToBytes(type + "\0")))
|
2020-04-07 10:42:29 +02:00
|
|
|
.put(timestampRoundedToBuffer(wf.startStamp))
|
|
|
|
.put(timestampRoundedToBuffer(wf.endStamp))
|
2019-11-28 00:46:34 +01:00
|
|
|
.put(amountToBuffer(wf.wireFee))
|
2019-12-05 23:07:46 +01:00
|
|
|
.put(amountToBuffer(wf.closingFee))
|
2022-03-07 12:09:38 +01:00
|
|
|
.put(amountToBuffer(wf.wadFee))
|
2019-11-28 00:46:34 +01:00
|
|
|
.build();
|
|
|
|
const sig = decodeCrock(wf.sig);
|
|
|
|
const pub = decodeCrock(masterPub);
|
2022-03-23 21:24:23 +01:00
|
|
|
return { valid: eddsaVerify(p, sig, pub) };
|
|
|
|
},
|
2019-08-15 23:34:08 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if the signature of a denomination is valid.
|
|
|
|
*/
|
2021-11-16 17:20:36 +01:00
|
|
|
async isValidDenom(
|
2022-03-23 21:24:23 +01:00
|
|
|
tci: TalerCryptoInterfaceR,
|
|
|
|
req: DenominationValidationRequest,
|
|
|
|
): Promise<ValidationResult> {
|
|
|
|
const { masterPub, denom } = req;
|
2021-10-18 21:48:22 +02:00
|
|
|
const p = buildSigPS(TalerSignaturePurpose.MASTER_DENOMINATION_KEY_VALIDITY)
|
2019-11-28 00:46:34 +01:00
|
|
|
.put(decodeCrock(masterPub))
|
2020-04-07 10:42:29 +02:00
|
|
|
.put(timestampRoundedToBuffer(denom.stampStart))
|
|
|
|
.put(timestampRoundedToBuffer(denom.stampExpireWithdraw))
|
|
|
|
.put(timestampRoundedToBuffer(denom.stampExpireDeposit))
|
|
|
|
.put(timestampRoundedToBuffer(denom.stampExpireLegal))
|
2019-11-28 00:46:34 +01:00
|
|
|
.put(amountToBuffer(denom.value))
|
|
|
|
.put(amountToBuffer(denom.feeWithdraw))
|
|
|
|
.put(amountToBuffer(denom.feeDeposit))
|
|
|
|
.put(amountToBuffer(denom.feeRefresh))
|
|
|
|
.put(amountToBuffer(denom.feeRefund))
|
|
|
|
.put(decodeCrock(denom.denomPubHash))
|
|
|
|
.build();
|
|
|
|
const sig = decodeCrock(denom.masterSig);
|
|
|
|
const pub = decodeCrock(masterPub);
|
2021-08-06 17:15:46 +02:00
|
|
|
const res = eddsaVerify(p, sig, pub);
|
2022-03-23 21:24:23 +01:00
|
|
|
return { valid: res };
|
|
|
|
},
|
|
|
|
|
|
|
|
async isValidWireAccount(
|
|
|
|
tci: TalerCryptoInterfaceR,
|
|
|
|
req: WireAccountValidationRequest,
|
|
|
|
): Promise<ValidationResult> {
|
|
|
|
const { sig, masterPub, paytoUri } = req;
|
2022-03-07 12:09:38 +01:00
|
|
|
const paytoHash = hashTruncate32(stringToBytes(paytoUri + "\0"));
|
2022-02-21 12:40:51 +01:00
|
|
|
const p = buildSigPS(TalerSignaturePurpose.MASTER_WIRE_DETAILS)
|
|
|
|
.put(paytoHash)
|
|
|
|
.build();
|
2022-03-23 21:24:23 +01:00
|
|
|
return { valid: eddsaVerify(p, decodeCrock(sig), decodeCrock(masterPub)) };
|
|
|
|
},
|
|
|
|
|
|
|
|
async isValidContractTermsSignature(
|
|
|
|
tci: TalerCryptoInterfaceR,
|
|
|
|
req: ContractTermsValidationRequest,
|
|
|
|
): Promise<ValidationResult> {
|
|
|
|
const cthDec = decodeCrock(req.contractTermsHash);
|
2021-10-18 21:48:22 +02:00
|
|
|
const p = buildSigPS(TalerSignaturePurpose.MERCHANT_CONTRACT)
|
2020-11-03 17:39:30 +01:00
|
|
|
.put(cthDec)
|
|
|
|
.build();
|
2022-03-23 21:24:23 +01:00
|
|
|
return {
|
|
|
|
valid: eddsaVerify(p, decodeCrock(req.sig), decodeCrock(req.merchantPub)),
|
|
|
|
};
|
|
|
|
},
|
2020-11-03 17:39:30 +01:00
|
|
|
|
2019-08-15 23:34:08 +02:00
|
|
|
/**
|
|
|
|
* Create a new EdDSA key pair.
|
|
|
|
*/
|
2022-03-23 21:24:23 +01:00
|
|
|
async createEddsaKeypair(tci: TalerCryptoInterfaceR): Promise<EddsaKeypair> {
|
2019-11-28 00:46:34 +01:00
|
|
|
const pair = createEddsaKeyPair();
|
|
|
|
return {
|
|
|
|
priv: encodeCrock(pair.eddsaPriv),
|
|
|
|
pub: encodeCrock(pair.eddsaPub),
|
|
|
|
};
|
2022-03-23 21:24:23 +01:00
|
|
|
},
|
2019-08-15 23:34:08 +02:00
|
|
|
|
2022-03-23 21:24:23 +01:00
|
|
|
async eddsaGetPublic(
|
|
|
|
tci: TalerCryptoInterfaceR,
|
|
|
|
req: EddsaGetPublicRequest,
|
|
|
|
): Promise<EddsaKeypair> {
|
2021-09-17 20:48:33 +02:00
|
|
|
return {
|
2022-03-23 21:24:23 +01:00
|
|
|
priv: req.priv,
|
|
|
|
pub: encodeCrock(eddsaGetPublic(decodeCrock(req.priv))),
|
2021-10-18 21:48:22 +02:00
|
|
|
};
|
2022-03-23 21:24:23 +01:00
|
|
|
},
|
2021-09-17 20:48:33 +02:00
|
|
|
|
2022-03-23 21:24:23 +01:00
|
|
|
async unblindDenominationSignature(
|
|
|
|
tci: TalerCryptoInterfaceR,
|
|
|
|
req: UnblindDenominationSignatureRequest,
|
|
|
|
): Promise<UnblindedSignature> {
|
2022-03-14 18:31:30 +01:00
|
|
|
if (req.evSig.cipher === DenomKeyType.Rsa) {
|
|
|
|
if (req.planchet.denomPub.cipher !== DenomKeyType.Rsa) {
|
|
|
|
throw new Error(
|
|
|
|
"planchet cipher does not match blind signature cipher",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
const denomSig = rsaUnblind(
|
|
|
|
decodeCrock(req.evSig.blinded_rsa_signature),
|
|
|
|
decodeCrock(req.planchet.denomPub.rsa_public_key),
|
|
|
|
decodeCrock(req.planchet.blindingKey),
|
|
|
|
);
|
|
|
|
return {
|
|
|
|
cipher: DenomKeyType.Rsa,
|
|
|
|
rsa_signature: encodeCrock(denomSig),
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
throw Error(`unblinding for cipher ${req.evSig.cipher} not implemented`);
|
|
|
|
}
|
2022-03-23 21:24:23 +01:00
|
|
|
},
|
2022-03-14 18:31:30 +01:00
|
|
|
|
2019-08-15 23:34:08 +02:00
|
|
|
/**
|
|
|
|
* Unblind a blindly signed value.
|
|
|
|
*/
|
2022-03-23 21:24:23 +01:00
|
|
|
async rsaUnblind(
|
|
|
|
tci: TalerCryptoInterfaceR,
|
|
|
|
req: RsaUnblindRequest,
|
|
|
|
): Promise<RsaUnblindResponse> {
|
2019-11-28 00:46:34 +01:00
|
|
|
const denomSig = rsaUnblind(
|
2022-03-23 21:24:23 +01:00
|
|
|
decodeCrock(req.blindedSig),
|
|
|
|
decodeCrock(req.pk),
|
|
|
|
decodeCrock(req.bk),
|
2019-08-15 23:34:08 +02:00
|
|
|
);
|
2022-03-23 21:24:23 +01:00
|
|
|
return { sig: encodeCrock(denomSig) };
|
|
|
|
},
|
2019-08-15 23:34:08 +02:00
|
|
|
|
2019-12-05 19:38:19 +01:00
|
|
|
/**
|
|
|
|
* Unblind a blindly signed value.
|
|
|
|
*/
|
2022-03-23 21:24:23 +01:00
|
|
|
async rsaVerify(
|
|
|
|
tci: TalerCryptoInterfaceR,
|
|
|
|
req: RsaVerificationRequest,
|
|
|
|
): Promise<ValidationResult> {
|
|
|
|
return {
|
|
|
|
valid: rsaVerify(
|
|
|
|
hash(decodeCrock(req.hm)),
|
|
|
|
decodeCrock(req.sig),
|
|
|
|
decodeCrock(req.pk),
|
|
|
|
),
|
|
|
|
};
|
|
|
|
},
|
2019-12-05 19:38:19 +01:00
|
|
|
|
2019-08-15 23:34:08 +02:00
|
|
|
/**
|
|
|
|
* Generate updated coins (to store in the database)
|
|
|
|
* and deposit permissions for each given coin.
|
|
|
|
*/
|
2021-12-09 10:39:50 +01:00
|
|
|
async signDepositPermission(
|
2022-03-23 21:24:23 +01:00
|
|
|
tci: TalerCryptoInterfaceR,
|
2021-12-09 10:39:50 +01:00
|
|
|
depositInfo: DepositInfo,
|
|
|
|
): Promise<CoinDepositPermission> {
|
2021-11-17 10:23:22 +01:00
|
|
|
// FIXME: put extensions here if used
|
|
|
|
const hExt = new Uint8Array(64);
|
2022-02-21 12:40:51 +01:00
|
|
|
const hAgeCommitment = new Uint8Array(32);
|
2021-11-27 20:56:58 +01:00
|
|
|
let d: Uint8Array;
|
|
|
|
if (depositInfo.denomKeyType === DenomKeyType.Rsa) {
|
|
|
|
d = buildSigPS(TalerSignaturePurpose.WALLET_COIN_DEPOSIT)
|
|
|
|
.put(decodeCrock(depositInfo.contractTermsHash))
|
2022-02-21 12:40:51 +01:00
|
|
|
.put(hAgeCommitment)
|
2021-11-27 20:56:58 +01:00
|
|
|
.put(hExt)
|
|
|
|
.put(decodeCrock(depositInfo.wireInfoHash))
|
|
|
|
.put(decodeCrock(depositInfo.denomPubHash))
|
|
|
|
.put(timestampRoundedToBuffer(depositInfo.timestamp))
|
|
|
|
.put(timestampRoundedToBuffer(depositInfo.refundDeadline))
|
|
|
|
.put(amountToBuffer(depositInfo.spendAmount))
|
|
|
|
.put(amountToBuffer(depositInfo.feeDeposit))
|
|
|
|
.put(decodeCrock(depositInfo.merchantPub))
|
|
|
|
.build();
|
|
|
|
} else {
|
|
|
|
throw Error("unsupported exchange protocol version");
|
|
|
|
}
|
2022-03-23 21:24:23 +01:00
|
|
|
const coinSigRes = await this.eddsaSign(tci, {
|
2021-12-09 10:39:50 +01:00
|
|
|
msg: encodeCrock(d),
|
|
|
|
priv: depositInfo.coinPriv,
|
|
|
|
});
|
2019-08-15 23:34:08 +02:00
|
|
|
|
2021-11-27 20:56:58 +01:00
|
|
|
if (depositInfo.denomKeyType === DenomKeyType.Rsa) {
|
|
|
|
const s: CoinDepositPermission = {
|
|
|
|
coin_pub: depositInfo.coinPub,
|
2021-12-09 10:39:50 +01:00
|
|
|
coin_sig: coinSigRes.sig,
|
2021-11-27 20:56:58 +01:00
|
|
|
contribution: Amounts.stringify(depositInfo.spendAmount),
|
|
|
|
h_denom: depositInfo.denomPubHash,
|
|
|
|
exchange_url: depositInfo.exchangeBaseUrl,
|
|
|
|
ub_sig: {
|
|
|
|
cipher: DenomKeyType.Rsa,
|
|
|
|
rsa_signature: depositInfo.denomSig.rsa_signature,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
return s;
|
|
|
|
} else {
|
2022-02-21 12:40:51 +01:00
|
|
|
throw Error(
|
|
|
|
`unsupported denomination cipher (${depositInfo.denomKeyType})`,
|
|
|
|
);
|
2021-11-27 20:56:58 +01:00
|
|
|
}
|
2022-03-23 21:24:23 +01:00
|
|
|
},
|
2019-08-15 23:34:08 +02:00
|
|
|
|
2021-11-16 17:20:36 +01:00
|
|
|
async deriveRefreshSession(
|
2022-03-23 21:24:23 +01:00
|
|
|
tci: TalerCryptoInterfaceR,
|
2020-12-14 16:44:42 +01:00
|
|
|
req: DeriveRefreshSessionRequest,
|
2021-11-16 17:20:36 +01:00
|
|
|
): Promise<DerivedRefreshSession> {
|
2020-12-14 16:44:42 +01:00
|
|
|
const {
|
|
|
|
newCoinDenoms,
|
|
|
|
feeRefresh: meltFee,
|
|
|
|
kappa,
|
|
|
|
meltCoinDenomPubHash,
|
|
|
|
meltCoinPriv,
|
|
|
|
meltCoinPub,
|
|
|
|
sessionSecretSeed: refreshSessionSecretSeed,
|
|
|
|
} = req;
|
|
|
|
|
|
|
|
const currency = newCoinDenoms[0].value.currency;
|
2020-05-11 14:33:25 +02:00
|
|
|
let valueWithFee = Amounts.getZero(currency);
|
2019-08-15 23:34:08 +02:00
|
|
|
|
2020-12-14 16:44:42 +01:00
|
|
|
for (const ncd of newCoinDenoms) {
|
|
|
|
const t = Amounts.add(ncd.value, ncd.feeWithdraw).amount;
|
2020-06-03 13:16:25 +02:00
|
|
|
valueWithFee = Amounts.add(
|
|
|
|
valueWithFee,
|
|
|
|
Amounts.mult(t, ncd.count).amount,
|
|
|
|
).amount;
|
2019-08-15 23:34:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// melt fee
|
|
|
|
valueWithFee = Amounts.add(valueWithFee, meltFee).amount;
|
|
|
|
|
2019-11-28 00:46:34 +01:00
|
|
|
const sessionHc = createHashContext();
|
2019-08-15 23:34:08 +02:00
|
|
|
|
|
|
|
const transferPubs: string[] = [];
|
|
|
|
const transferPrivs: string[] = [];
|
|
|
|
|
2021-08-24 15:43:06 +02:00
|
|
|
const planchetsForGammas: RefreshPlanchetInfo[][] = [];
|
2019-08-15 23:34:08 +02:00
|
|
|
|
|
|
|
for (let i = 0; i < kappa; i++) {
|
2020-12-14 16:44:42 +01:00
|
|
|
const transferKeyPair = setupRefreshTransferPub(
|
|
|
|
decodeCrock(refreshSessionSecretSeed),
|
|
|
|
i,
|
|
|
|
);
|
2019-11-28 00:46:34 +01:00
|
|
|
sessionHc.update(transferKeyPair.ecdhePub);
|
|
|
|
transferPrivs.push(encodeCrock(transferKeyPair.ecdhePriv));
|
|
|
|
transferPubs.push(encodeCrock(transferKeyPair.ecdhePub));
|
2019-08-15 23:34:08 +02:00
|
|
|
}
|
|
|
|
|
2020-12-14 16:44:42 +01:00
|
|
|
for (const denomSel of newCoinDenoms) {
|
2020-05-11 14:33:25 +02:00
|
|
|
for (let i = 0; i < denomSel.count; i++) {
|
2022-02-21 12:40:51 +01:00
|
|
|
if (denomSel.denomPub.cipher === DenomKeyType.Rsa) {
|
|
|
|
const denomPubHash = hashDenomPub(denomSel.denomPub);
|
|
|
|
sessionHc.update(denomPubHash);
|
2021-11-27 20:56:58 +01:00
|
|
|
} else {
|
2022-02-21 12:40:51 +01:00
|
|
|
throw new Error();
|
2021-11-17 10:23:22 +01:00
|
|
|
}
|
2020-05-11 14:33:25 +02:00
|
|
|
}
|
2019-08-15 23:34:08 +02:00
|
|
|
}
|
|
|
|
|
2020-12-14 16:44:42 +01:00
|
|
|
sessionHc.update(decodeCrock(meltCoinPub));
|
2019-11-28 00:46:34 +01:00
|
|
|
sessionHc.update(amountToBuffer(valueWithFee));
|
2022-02-21 12:40:51 +01:00
|
|
|
|
2019-08-15 23:34:08 +02:00
|
|
|
for (let i = 0; i < kappa; i++) {
|
2021-08-24 15:43:06 +02:00
|
|
|
const planchets: RefreshPlanchetInfo[] = [];
|
2020-12-14 16:44:42 +01:00
|
|
|
for (let j = 0; j < newCoinDenoms.length; j++) {
|
|
|
|
const denomSel = newCoinDenoms[j];
|
2020-05-11 14:33:25 +02:00
|
|
|
for (let k = 0; k < denomSel.count; k++) {
|
2021-11-16 17:20:36 +01:00
|
|
|
const coinIndex = planchets.length;
|
2022-03-24 01:10:34 +01:00
|
|
|
const transferSecretRes = await tci.keyExchangeEcdheEddsa(tci, {
|
|
|
|
ecdhePriv: transferPrivs[i],
|
|
|
|
eddsaPub: meltCoinPub,
|
|
|
|
});
|
2021-11-16 17:20:36 +01:00
|
|
|
let coinPub: Uint8Array;
|
|
|
|
let coinPriv: Uint8Array;
|
|
|
|
let blindingFactor: Uint8Array;
|
2022-03-24 01:10:34 +01:00
|
|
|
let fresh: FreshCoinEncoded = await tci.setupRefreshPlanchet(tci, {
|
|
|
|
coinNumber: coinIndex,
|
|
|
|
transferSecret: transferSecretRes.h,
|
|
|
|
});
|
|
|
|
coinPriv = decodeCrock(fresh.coinPriv);
|
|
|
|
coinPub = decodeCrock(fresh.coinPub);
|
|
|
|
blindingFactor = decodeCrock(fresh.bks);
|
2022-02-21 12:40:51 +01:00
|
|
|
const coinPubHash = hash(coinPub);
|
|
|
|
if (denomSel.denomPub.cipher !== DenomKeyType.Rsa) {
|
2021-11-27 20:56:58 +01:00
|
|
|
throw Error("unsupported cipher, can't create refresh session");
|
2021-11-17 10:23:22 +01:00
|
|
|
}
|
2022-03-24 01:10:34 +01:00
|
|
|
const blindResult = await tci.rsaBlind(tci, {
|
|
|
|
bks: encodeCrock(blindingFactor),
|
|
|
|
hm: encodeCrock(coinPubHash),
|
|
|
|
pub: denomSel.denomPub.rsa_public_key,
|
|
|
|
});
|
2022-02-21 12:40:51 +01:00
|
|
|
const coinEv: CoinEnvelope = {
|
|
|
|
cipher: DenomKeyType.Rsa,
|
2022-03-24 01:10:34 +01:00
|
|
|
rsa_blinded_planchet: blindResult.blinded,
|
2022-02-21 12:40:51 +01:00
|
|
|
};
|
|
|
|
const coinEvHash = hashCoinEv(
|
|
|
|
coinEv,
|
|
|
|
encodeCrock(hashDenomPub(denomSel.denomPub)),
|
|
|
|
);
|
2021-08-24 15:43:06 +02:00
|
|
|
const planchet: RefreshPlanchetInfo = {
|
2020-05-11 14:33:25 +02:00
|
|
|
blindingKey: encodeCrock(blindingFactor),
|
2022-02-21 12:40:51 +01:00
|
|
|
coinEv,
|
|
|
|
coinPriv: encodeCrock(coinPriv),
|
|
|
|
coinPub: encodeCrock(coinPub),
|
|
|
|
coinEvHash: encodeCrock(coinEvHash),
|
2020-05-11 14:33:25 +02:00
|
|
|
};
|
|
|
|
planchets.push(planchet);
|
2022-02-21 12:40:51 +01:00
|
|
|
hashCoinEvInner(coinEv, sessionHc);
|
2020-05-11 14:33:25 +02:00
|
|
|
}
|
2019-08-15 23:34:08 +02:00
|
|
|
}
|
2019-11-30 00:36:20 +01:00
|
|
|
planchetsForGammas.push(planchets);
|
2019-08-15 23:34:08 +02:00
|
|
|
}
|
|
|
|
|
2019-11-28 00:46:34 +01:00
|
|
|
const sessionHash = sessionHc.finish();
|
2022-01-05 20:29:55 +01:00
|
|
|
let confirmData: Uint8Array;
|
2022-02-21 12:40:51 +01:00
|
|
|
// FIXME: fill in age commitment
|
|
|
|
const hAgeCommitment = new Uint8Array(32);
|
|
|
|
confirmData = buildSigPS(TalerSignaturePurpose.WALLET_COIN_MELT)
|
|
|
|
.put(sessionHash)
|
|
|
|
.put(decodeCrock(meltCoinDenomPubHash))
|
|
|
|
.put(hAgeCommitment)
|
|
|
|
.put(amountToBuffer(valueWithFee))
|
|
|
|
.put(amountToBuffer(meltFee))
|
|
|
|
.build();
|
2019-08-15 23:34:08 +02:00
|
|
|
|
2022-03-23 21:24:23 +01:00
|
|
|
const confirmSigResp = await tci.eddsaSign(tci, {
|
2021-12-09 10:39:50 +01:00
|
|
|
msg: encodeCrock(confirmData),
|
|
|
|
priv: meltCoinPriv,
|
|
|
|
});
|
2019-08-15 23:34:08 +02:00
|
|
|
|
2020-12-14 16:44:42 +01:00
|
|
|
const refreshSession: DerivedRefreshSession = {
|
2021-12-09 10:39:50 +01:00
|
|
|
confirmSig: confirmSigResp.sig,
|
2019-11-28 00:46:34 +01:00
|
|
|
hash: encodeCrock(sessionHash),
|
2020-12-14 16:44:42 +01:00
|
|
|
meltCoinPub: meltCoinPub,
|
2019-11-30 00:36:20 +01:00
|
|
|
planchetsForGammas: planchetsForGammas,
|
2019-08-15 23:34:08 +02:00
|
|
|
transferPrivs,
|
|
|
|
transferPubs,
|
2020-12-14 16:44:42 +01:00
|
|
|
meltValueWithFee: valueWithFee,
|
2019-08-15 23:34:08 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
return refreshSession;
|
2022-03-23 21:24:23 +01:00
|
|
|
},
|
2019-08-15 23:34:08 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Hash a string including the zero terminator.
|
|
|
|
*/
|
2022-03-23 21:24:23 +01:00
|
|
|
async hashString(
|
|
|
|
tci: TalerCryptoInterfaceR,
|
|
|
|
req: HashStringRequest,
|
|
|
|
): Promise<HashStringResult> {
|
|
|
|
const b = stringToBytes(req.str + "\0");
|
|
|
|
return { h: encodeCrock(hash(b)) };
|
|
|
|
},
|
2019-08-15 23:34:08 +02:00
|
|
|
|
2021-12-09 10:39:50 +01:00
|
|
|
async signCoinLink(
|
2022-03-23 21:24:23 +01:00
|
|
|
tci: TalerCryptoInterfaceR,
|
|
|
|
req: SignCoinLinkRequest,
|
|
|
|
): Promise<EddsaSigningResult> {
|
|
|
|
const coinEvHash = hashCoinEv(req.coinEv, req.newDenomHash);
|
2022-02-21 12:40:51 +01:00
|
|
|
// FIXME: fill in
|
|
|
|
const hAgeCommitment = new Uint8Array(32);
|
2021-10-18 21:48:22 +02:00
|
|
|
const coinLink = buildSigPS(TalerSignaturePurpose.WALLET_COIN_LINK)
|
2022-03-23 21:24:23 +01:00
|
|
|
.put(decodeCrock(req.newDenomHash))
|
|
|
|
.put(decodeCrock(req.transferPub))
|
2022-02-21 12:40:51 +01:00
|
|
|
.put(hAgeCommitment)
|
2019-11-28 00:46:34 +01:00
|
|
|
.put(coinEvHash)
|
|
|
|
.build();
|
2022-03-23 21:24:23 +01:00
|
|
|
return tci.eddsaSign(tci, {
|
2021-12-09 10:39:50 +01:00
|
|
|
msg: encodeCrock(coinLink),
|
2022-03-23 21:24:23 +01:00
|
|
|
priv: req.oldCoinPriv,
|
2021-12-09 10:39:50 +01:00
|
|
|
});
|
2022-03-23 21:24:23 +01:00
|
|
|
},
|
2019-08-15 23:34:08 +02:00
|
|
|
|
2022-03-23 21:24:23 +01:00
|
|
|
async makeSyncSignature(
|
|
|
|
tci: TalerCryptoInterfaceR,
|
|
|
|
req: MakeSyncSignatureRequest,
|
|
|
|
): Promise<EddsaSigningResult> {
|
2020-12-02 14:55:04 +01:00
|
|
|
const hNew = decodeCrock(req.newHash);
|
|
|
|
let hOld: Uint8Array;
|
|
|
|
if (req.oldHash) {
|
|
|
|
hOld = decodeCrock(req.oldHash);
|
|
|
|
} else {
|
|
|
|
hOld = new Uint8Array(64);
|
|
|
|
}
|
2021-10-18 21:48:22 +02:00
|
|
|
const sigBlob = buildSigPS(TalerSignaturePurpose.SYNC_BACKUP_UPLOAD)
|
2020-12-02 14:55:04 +01:00
|
|
|
.put(hOld)
|
|
|
|
.put(hNew)
|
|
|
|
.build();
|
|
|
|
const uploadSig = eddsaSign(sigBlob, decodeCrock(req.accountPriv));
|
2022-03-23 21:24:23 +01:00
|
|
|
return { sig: encodeCrock(uploadSig) };
|
|
|
|
},
|
2022-03-24 01:10:34 +01:00
|
|
|
async keyExchangeEcdheEddsa(
|
|
|
|
tci: TalerCryptoInterfaceR,
|
|
|
|
req: KeyExchangeEcdheEddsaRequest,
|
|
|
|
): Promise<KeyExchangeResult> {
|
|
|
|
return {
|
|
|
|
h: encodeCrock(
|
|
|
|
keyExchangeEcdheEddsa(
|
|
|
|
decodeCrock(req.ecdhePriv),
|
|
|
|
decodeCrock(req.eddsaPub),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
};
|
|
|
|
},
|
2022-03-23 21:24:23 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
function amountToBuffer(amount: AmountJson): Uint8Array {
|
|
|
|
const buffer = new ArrayBuffer(8 + 4 + 12);
|
|
|
|
const dvbuf = new DataView(buffer);
|
|
|
|
const u8buf = new Uint8Array(buffer);
|
|
|
|
const curr = stringToBytes(amount.currency);
|
|
|
|
if (typeof dvbuf.setBigUint64 !== "undefined") {
|
|
|
|
dvbuf.setBigUint64(0, BigInt(amount.value));
|
|
|
|
} else {
|
|
|
|
const arr = bigint(amount.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, amount.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]);
|
|
|
|
}
|
2020-12-02 14:55:04 +01:00
|
|
|
}
|
2022-03-23 21:24:23 +01:00
|
|
|
return new Uint8Array(b);
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface EddsaSignRequest {
|
|
|
|
msg: string;
|
|
|
|
priv: string;
|
2019-08-15 23:34:08 +02:00
|
|
|
}
|
2022-03-23 21:24:23 +01:00
|
|
|
|
|
|
|
export interface EddsaSignResponse {
|
|
|
|
sig: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
export const nativeCrypto: TalerCryptoInterface = Object.fromEntries(
|
|
|
|
Object.keys(nativeCryptoR).map((name) => {
|
|
|
|
return [
|
|
|
|
name,
|
|
|
|
(req: any) =>
|
|
|
|
nativeCryptoR[name as keyof TalerCryptoInterfaceR](nativeCryptoR, req),
|
|
|
|
];
|
|
|
|
}),
|
|
|
|
) as any;
|