wallet-core: deterministic p2p contract encryption
This commit is contained in:
parent
9d35a7dc9b
commit
474a171f5e
@ -1465,6 +1465,7 @@ export function encryptContractForMerge(
|
|||||||
contractPriv: ContractPrivateKey,
|
contractPriv: ContractPrivateKey,
|
||||||
mergePriv: MergePrivateKey,
|
mergePriv: MergePrivateKey,
|
||||||
contractTerms: any,
|
contractTerms: any,
|
||||||
|
nonce: EncryptionNonce,
|
||||||
): Promise<OpaqueData> {
|
): Promise<OpaqueData> {
|
||||||
const contractTermsCanon = canonicalJson(contractTerms) + "\0";
|
const contractTermsCanon = canonicalJson(contractTerms) + "\0";
|
||||||
const contractTermsBytes = stringToBytes(contractTermsCanon);
|
const contractTermsBytes = stringToBytes(contractTermsCanon);
|
||||||
@ -1476,13 +1477,14 @@ export function encryptContractForMerge(
|
|||||||
contractTermsCompressed,
|
contractTermsCompressed,
|
||||||
]);
|
]);
|
||||||
const key = keyExchangeEcdhEddsa(contractPriv, pursePub);
|
const key = keyExchangeEcdhEddsa(contractPriv, pursePub);
|
||||||
return encryptWithDerivedKey(getRandomBytesF(24), key, data, mergeSalt);
|
return encryptWithDerivedKey(nonce, key, data, mergeSalt);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function encryptContractForDeposit(
|
export function encryptContractForDeposit(
|
||||||
pursePub: PursePublicKey,
|
pursePub: PursePublicKey,
|
||||||
contractPriv: ContractPrivateKey,
|
contractPriv: ContractPrivateKey,
|
||||||
contractTerms: any,
|
contractTerms: any,
|
||||||
|
nonce: EncryptionNonce,
|
||||||
): Promise<OpaqueData> {
|
): Promise<OpaqueData> {
|
||||||
const contractTermsCanon = canonicalJson(contractTerms) + "\0";
|
const contractTermsCanon = canonicalJson(contractTerms) + "\0";
|
||||||
const contractTermsBytes = stringToBytes(contractTermsCanon);
|
const contractTermsBytes = stringToBytes(contractTermsCanon);
|
||||||
@ -1493,7 +1495,7 @@ export function encryptContractForDeposit(
|
|||||||
contractTermsCompressed,
|
contractTermsCompressed,
|
||||||
]);
|
]);
|
||||||
const key = keyExchangeEcdhEddsa(contractPriv, pursePub);
|
const key = keyExchangeEcdhEddsa(contractPriv, pursePub);
|
||||||
return encryptWithDerivedKey(getRandomBytesF(24), key, data, depositSalt);
|
return encryptWithDerivedKey(nonce, key, data, depositSalt);
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DecryptForMergeResult {
|
export interface DecryptForMergeResult {
|
||||||
|
@ -1495,6 +1495,7 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
|
|||||||
decodeCrock(req.contractPriv),
|
decodeCrock(req.contractPriv),
|
||||||
decodeCrock(req.mergePriv),
|
decodeCrock(req.mergePriv),
|
||||||
req.contractTerms,
|
req.contractTerms,
|
||||||
|
decodeCrock(req.nonce),
|
||||||
);
|
);
|
||||||
const sigBlob = buildSigPS(TalerSignaturePurpose.WALLET_PURSE_ECONTRACT)
|
const sigBlob = buildSigPS(TalerSignaturePurpose.WALLET_PURSE_ECONTRACT)
|
||||||
.put(hash(enc))
|
.put(hash(enc))
|
||||||
@ -1531,6 +1532,7 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
|
|||||||
decodeCrock(req.pursePub),
|
decodeCrock(req.pursePub),
|
||||||
decodeCrock(req.contractPriv),
|
decodeCrock(req.contractPriv),
|
||||||
req.contractTerms,
|
req.contractTerms,
|
||||||
|
decodeCrock(req.nonce),
|
||||||
);
|
);
|
||||||
const sigBlob = buildSigPS(TalerSignaturePurpose.WALLET_PURSE_ECONTRACT)
|
const sigBlob = buildSigPS(TalerSignaturePurpose.WALLET_PURSE_ECONTRACT)
|
||||||
.put(hash(enc))
|
.put(hash(enc))
|
||||||
|
@ -181,6 +181,7 @@ export interface EncryptContractRequest {
|
|||||||
pursePub: string;
|
pursePub: string;
|
||||||
pursePriv: string;
|
pursePriv: string;
|
||||||
mergePriv: string;
|
mergePriv: string;
|
||||||
|
nonce: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EncryptContractResponse {
|
export interface EncryptContractResponse {
|
||||||
@ -195,6 +196,8 @@ export interface EncryptContractForDepositRequest {
|
|||||||
|
|
||||||
pursePub: string;
|
pursePub: string;
|
||||||
pursePriv: string;
|
pursePriv: string;
|
||||||
|
|
||||||
|
nonce: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EncryptContractForDepositResponse {
|
export interface EncryptContractForDepositResponse {
|
||||||
|
@ -1839,6 +1839,14 @@ export interface PeerPushPaymentInitiationRecord {
|
|||||||
contractPriv: string;
|
contractPriv: string;
|
||||||
contractPub: string;
|
contractPub: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 24 byte nonce.
|
||||||
|
*/
|
||||||
|
contractEncNonce: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FIXME: Put those in a different object store!
|
||||||
|
*/
|
||||||
contractTerms: PeerContractTerms;
|
contractTerms: PeerContractTerms;
|
||||||
|
|
||||||
purseExpiration: TalerProtocolTimestamp;
|
purseExpiration: TalerProtocolTimestamp;
|
||||||
@ -1911,6 +1919,11 @@ export interface PeerPullPaymentInitiationRecord {
|
|||||||
contractPub: string;
|
contractPub: string;
|
||||||
contractPriv: string;
|
contractPriv: string;
|
||||||
|
|
||||||
|
contractEncNonce: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FIXME: Put in separate object store!
|
||||||
|
*/
|
||||||
contractTerms: PeerContractTerms;
|
contractTerms: PeerContractTerms;
|
||||||
|
|
||||||
mergeTimestamp: TalerPreciseTimestamp;
|
mergeTimestamp: TalerPreciseTimestamp;
|
||||||
|
@ -405,6 +405,7 @@ export async function processPeerPullCredit(
|
|||||||
contractTerms: pullIni.contractTerms,
|
contractTerms: pullIni.contractTerms,
|
||||||
pursePriv: pullIni.pursePriv,
|
pursePriv: pullIni.pursePriv,
|
||||||
pursePub: pullIni.pursePub,
|
pursePub: pullIni.pursePub,
|
||||||
|
nonce: pullIni.contractEncNonce,
|
||||||
});
|
});
|
||||||
|
|
||||||
const purseExpiration = pullIni.contractTerms.purse_expiration;
|
const purseExpiration = pullIni.contractTerms.purse_expiration;
|
||||||
@ -690,6 +691,8 @@ export async function initiatePeerPullPayment(
|
|||||||
const mergeReserveRowId = mergeReserveInfo.rowId;
|
const mergeReserveRowId = mergeReserveInfo.rowId;
|
||||||
checkDbInvariant(!!mergeReserveRowId);
|
checkDbInvariant(!!mergeReserveRowId);
|
||||||
|
|
||||||
|
const contractEncNonce = encodeCrock(getRandomBytes(24));
|
||||||
|
|
||||||
const wi = await getExchangeWithdrawalInfo(
|
const wi = await getExchangeWithdrawalInfo(
|
||||||
ws,
|
ws,
|
||||||
exchangeBaseUrl,
|
exchangeBaseUrl,
|
||||||
@ -711,6 +714,7 @@ export async function initiatePeerPullPayment(
|
|||||||
status: PeerPullPaymentInitiationStatus.PendingCreatePurse,
|
status: PeerPullPaymentInitiationStatus.PendingCreatePurse,
|
||||||
contractTerms: contractTerms,
|
contractTerms: contractTerms,
|
||||||
mergeTimestamp,
|
mergeTimestamp,
|
||||||
|
contractEncNonce,
|
||||||
mergeReserveRowId: mergeReserveRowId,
|
mergeReserveRowId: mergeReserveRowId,
|
||||||
contractPriv: contractKeyPair.priv,
|
contractPriv: contractKeyPair.priv,
|
||||||
contractPub: contractKeyPair.pub,
|
contractPub: contractKeyPair.pub,
|
||||||
|
@ -34,6 +34,10 @@ import {
|
|||||||
TransactionMinorState,
|
TransactionMinorState,
|
||||||
TransactionState,
|
TransactionState,
|
||||||
TransactionType,
|
TransactionType,
|
||||||
|
decodeCrock,
|
||||||
|
encodeCrock,
|
||||||
|
getRandomBytes,
|
||||||
|
hash,
|
||||||
j2s,
|
j2s,
|
||||||
stringifyTalerUri,
|
stringifyTalerUri,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
@ -57,11 +61,7 @@ import {
|
|||||||
OperationAttemptResultType,
|
OperationAttemptResultType,
|
||||||
constructTaskIdentifier,
|
constructTaskIdentifier,
|
||||||
} from "../util/retries.js";
|
} from "../util/retries.js";
|
||||||
import {
|
import { runLongpollAsync, spendCoins } from "./common.js";
|
||||||
runLongpollAsync,
|
|
||||||
spendCoins,
|
|
||||||
runOperationWithErrorReporting,
|
|
||||||
} from "./common.js";
|
|
||||||
import {
|
import {
|
||||||
constructTransactionIdentifier,
|
constructTransactionIdentifier,
|
||||||
notifyTransition,
|
notifyTransition,
|
||||||
@ -69,6 +69,7 @@ import {
|
|||||||
} from "./transactions.js";
|
} from "./transactions.js";
|
||||||
import { assertUnreachable } from "../util/assertUnreachable.js";
|
import { assertUnreachable } from "../util/assertUnreachable.js";
|
||||||
import { checkLogicInvariant } from "../util/invariants.js";
|
import { checkLogicInvariant } from "../util/invariants.js";
|
||||||
|
import { EncryptContractRequest } from "../crypto/cryptoTypes.js";
|
||||||
|
|
||||||
const logger = new Logger("pay-peer-push-debit.ts");
|
const logger = new Logger("pay-peer-push-debit.ts");
|
||||||
|
|
||||||
@ -100,6 +101,7 @@ async function processPeerPushDebitCreateReserve(
|
|||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
peerPushInitiation: PeerPushPaymentInitiationRecord,
|
peerPushInitiation: PeerPushPaymentInitiationRecord,
|
||||||
): Promise<OperationAttemptResult> {
|
): Promise<OperationAttemptResult> {
|
||||||
|
logger.info("processing peer-push-debit pending(create-reserve)");
|
||||||
const pursePub = peerPushInitiation.pursePub;
|
const pursePub = peerPushInitiation.pursePub;
|
||||||
const purseExpiration = peerPushInitiation.purseExpiration;
|
const purseExpiration = peerPushInitiation.purseExpiration;
|
||||||
const hContractTerms = peerPushInitiation.contractTermsHash;
|
const hContractTerms = peerPushInitiation.contractTermsHash;
|
||||||
@ -124,23 +126,34 @@ async function processPeerPushDebitCreateReserve(
|
|||||||
coins,
|
coins,
|
||||||
});
|
});
|
||||||
|
|
||||||
const econtractResp = await ws.cryptoApi.encryptContractForMerge({
|
const encryptContractRequest: EncryptContractRequest = {
|
||||||
contractTerms: peerPushInitiation.contractTerms,
|
contractTerms: peerPushInitiation.contractTerms,
|
||||||
mergePriv: peerPushInitiation.mergePriv,
|
mergePriv: peerPushInitiation.mergePriv,
|
||||||
pursePriv: peerPushInitiation.pursePriv,
|
pursePriv: peerPushInitiation.pursePriv,
|
||||||
pursePub: peerPushInitiation.pursePub,
|
pursePub: peerPushInitiation.pursePub,
|
||||||
contractPriv: peerPushInitiation.contractPriv,
|
contractPriv: peerPushInitiation.contractPriv,
|
||||||
contractPub: peerPushInitiation.contractPub,
|
contractPub: peerPushInitiation.contractPub,
|
||||||
});
|
nonce: peerPushInitiation.contractEncNonce,
|
||||||
|
};
|
||||||
|
|
||||||
|
logger.info(`encrypt contract request: ${j2s(encryptContractRequest)}`);
|
||||||
|
|
||||||
|
const econtractResp = await ws.cryptoApi.encryptContractForMerge(
|
||||||
|
encryptContractRequest,
|
||||||
|
);
|
||||||
|
|
||||||
|
const econtractHash = encodeCrock(
|
||||||
|
hash(decodeCrock(econtractResp.econtract.econtract)),
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.info(`econtract hash: ${econtractHash}`);
|
||||||
|
|
||||||
const createPurseUrl = new URL(
|
const createPurseUrl = new URL(
|
||||||
`purses/${peerPushInitiation.pursePub}/create`,
|
`purses/${peerPushInitiation.pursePub}/create`,
|
||||||
peerPushInitiation.exchangeBaseUrl,
|
peerPushInitiation.exchangeBaseUrl,
|
||||||
);
|
);
|
||||||
|
|
||||||
const httpResp = await ws.http.fetch(createPurseUrl.href, {
|
const reqBody = {
|
||||||
method: "POST",
|
|
||||||
body: {
|
|
||||||
amount: peerPushInitiation.amount,
|
amount: peerPushInitiation.amount,
|
||||||
merge_pub: peerPushInitiation.mergePub,
|
merge_pub: peerPushInitiation.mergePub,
|
||||||
purse_sig: purseSigResp.sig,
|
purse_sig: purseSigResp.sig,
|
||||||
@ -149,7 +162,13 @@ async function processPeerPushDebitCreateReserve(
|
|||||||
deposits: depositSigsResp.deposits,
|
deposits: depositSigsResp.deposits,
|
||||||
min_age: 0,
|
min_age: 0,
|
||||||
econtract: econtractResp.econtract,
|
econtract: econtractResp.econtract,
|
||||||
},
|
};
|
||||||
|
|
||||||
|
logger.info(`request body: ${j2s(reqBody)}`);
|
||||||
|
|
||||||
|
const httpResp = await ws.http.fetch(createPurseUrl.href, {
|
||||||
|
method: "POST",
|
||||||
|
body: reqBody,
|
||||||
});
|
});
|
||||||
|
|
||||||
const resp = await httpResp.json();
|
const resp = await httpResp.json();
|
||||||
@ -157,24 +176,16 @@ async function processPeerPushDebitCreateReserve(
|
|||||||
logger.info(`resp: ${j2s(resp)}`);
|
logger.info(`resp: ${j2s(resp)}`);
|
||||||
|
|
||||||
if (httpResp.status !== HttpStatusCode.Ok) {
|
if (httpResp.status !== HttpStatusCode.Ok) {
|
||||||
|
// FIXME: do proper error reporting
|
||||||
throw Error("got error response from exchange");
|
throw Error("got error response from exchange");
|
||||||
}
|
}
|
||||||
|
|
||||||
await ws.db
|
await transitionPeerPushDebitTransaction(ws, pursePub, {
|
||||||
.mktx((x) => [x.peerPushPaymentInitiations])
|
stFrom: PeerPushPaymentInitiationStatus.PendingCreatePurse,
|
||||||
.runReadWrite(async (tx) => {
|
stTo: PeerPushPaymentInitiationStatus.PendingReady,
|
||||||
const ppi = await tx.peerPushPaymentInitiations.get(pursePub);
|
|
||||||
if (!ppi) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ppi.status = PeerPushPaymentInitiationStatus.Done;
|
|
||||||
await tx.peerPushPaymentInitiations.put(ppi);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return OperationAttemptResult.finishedEmpty();
|
||||||
type: OperationAttemptResultType.Finished,
|
|
||||||
result: undefined,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function processPeerPushDebitAbortingDeletePurse(
|
async function processPeerPushDebitAbortingDeletePurse(
|
||||||
@ -278,6 +289,7 @@ async function transitionPeerPushDebitTransaction(
|
|||||||
const oldTxState = computePeerPushDebitTransactionState(ppiRec);
|
const oldTxState = computePeerPushDebitTransactionState(ppiRec);
|
||||||
ppiRec.status = transitionSpec.stTo;
|
ppiRec.status = transitionSpec.stTo;
|
||||||
const newTxState = computePeerPushDebitTransactionState(ppiRec);
|
const newTxState = computePeerPushDebitTransactionState(ppiRec);
|
||||||
|
// FIXME: We don't transition here?!
|
||||||
return {
|
return {
|
||||||
oldTxState,
|
oldTxState,
|
||||||
newTxState,
|
newTxState,
|
||||||
@ -341,6 +353,7 @@ async function processPeerPushDebitReady(
|
|||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
peerPushInitiation: PeerPushPaymentInitiationRecord,
|
peerPushInitiation: PeerPushPaymentInitiationRecord,
|
||||||
): Promise<OperationAttemptResult> {
|
): Promise<OperationAttemptResult> {
|
||||||
|
logger.info("processing peer-push-debit pending(ready)");
|
||||||
const pursePub = peerPushInitiation.pursePub;
|
const pursePub = peerPushInitiation.pursePub;
|
||||||
const retryTag = constructTaskIdentifier({
|
const retryTag = constructTaskIdentifier({
|
||||||
tag: PendingTaskType.PeerPushDebit,
|
tag: PendingTaskType.PeerPushDebit,
|
||||||
@ -434,6 +447,12 @@ export async function processPeerPushDebit(
|
|||||||
return processPeerPushDebitAbortingDeletePurse(ws, peerPushInitiation);
|
return processPeerPushDebitAbortingDeletePurse(ws, peerPushInitiation);
|
||||||
case PeerPushPaymentInitiationStatus.AbortingRefresh:
|
case PeerPushPaymentInitiationStatus.AbortingRefresh:
|
||||||
return processPeerPushDebitAbortingRefresh(ws, peerPushInitiation);
|
return processPeerPushDebitAbortingRefresh(ws, peerPushInitiation);
|
||||||
|
default: {
|
||||||
|
const txState = computePeerPushDebitTransactionState(peerPushInitiation);
|
||||||
|
logger.warn(
|
||||||
|
`not processing peer-push-debit transaction in state ${j2s(txState)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -482,7 +501,16 @@ export async function initiatePeerPushDebit(
|
|||||||
coinSelRes.result.coins,
|
coinSelRes.result.coins,
|
||||||
);
|
);
|
||||||
|
|
||||||
await ws.db
|
const pursePub = pursePair.pub;
|
||||||
|
|
||||||
|
const transactionId = constructTaskIdentifier({
|
||||||
|
tag: PendingTaskType.PeerPushDebit,
|
||||||
|
pursePub,
|
||||||
|
});
|
||||||
|
|
||||||
|
const contractEncNonce = encodeCrock(getRandomBytes(24));
|
||||||
|
|
||||||
|
const transitionInfo = await ws.db
|
||||||
.mktx((x) => [
|
.mktx((x) => [
|
||||||
x.exchanges,
|
x.exchanges,
|
||||||
x.contractTerms,
|
x.contractTerms,
|
||||||
@ -509,7 +537,7 @@ export async function initiatePeerPushDebit(
|
|||||||
refreshReason: RefreshReason.PayPeerPush,
|
refreshReason: RefreshReason.PayPeerPush,
|
||||||
});
|
});
|
||||||
|
|
||||||
await tx.peerPushPaymentInitiations.add({
|
const ppi: PeerPushPaymentInitiationRecord = {
|
||||||
amount: Amounts.stringify(instructedAmount),
|
amount: Amounts.stringify(instructedAmount),
|
||||||
contractPriv: contractKeyPair.priv,
|
contractPriv: contractKeyPair.priv,
|
||||||
contractPub: contractKeyPair.pub,
|
contractPub: contractKeyPair.pub,
|
||||||
@ -523,27 +551,28 @@ export async function initiatePeerPushDebit(
|
|||||||
timestampCreated: TalerPreciseTimestamp.now(),
|
timestampCreated: TalerPreciseTimestamp.now(),
|
||||||
status: PeerPushPaymentInitiationStatus.PendingCreatePurse,
|
status: PeerPushPaymentInitiationStatus.PendingCreatePurse,
|
||||||
contractTerms: contractTerms,
|
contractTerms: contractTerms,
|
||||||
|
contractEncNonce,
|
||||||
coinSel: {
|
coinSel: {
|
||||||
coinPubs: sel.coins.map((x) => x.coinPub),
|
coinPubs: sel.coins.map((x) => x.coinPub),
|
||||||
contributions: sel.coins.map((x) => x.contribution),
|
contributions: sel.coins.map((x) => x.contribution),
|
||||||
},
|
},
|
||||||
totalCost: Amounts.stringify(totalAmount),
|
totalCost: Amounts.stringify(totalAmount),
|
||||||
});
|
};
|
||||||
|
|
||||||
|
await tx.peerPushPaymentInitiations.add(ppi);
|
||||||
|
|
||||||
await tx.contractTerms.put({
|
await tx.contractTerms.put({
|
||||||
h: hContractTerms,
|
h: hContractTerms,
|
||||||
contractTermsRaw: contractTerms,
|
contractTermsRaw: contractTerms,
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
const taskId = constructTaskIdentifier({
|
const newTxState = computePeerPushDebitTransactionState(ppi);
|
||||||
tag: PendingTaskType.PeerPushDebit,
|
return {
|
||||||
pursePub: pursePair.pub,
|
oldTxState: { major: TransactionMajorState.None },
|
||||||
});
|
newTxState,
|
||||||
|
};
|
||||||
await runOperationWithErrorReporting(ws, taskId, async () => {
|
|
||||||
return await processPeerPushDebit(ws, pursePair.pub);
|
|
||||||
});
|
});
|
||||||
|
notifyTransition(ws, transactionId, transitionInfo);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
contractPriv: contractKeyPair.priv,
|
contractPriv: contractKeyPair.priv,
|
||||||
@ -903,4 +932,3 @@ export function computePeerPushDebitTransactionState(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user