taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit c085f242e6062070e7a6a5413e71e94ed49bea3c
parent acd8d1da4cdfa4d6036a4ec9928962dd2417bc50
Author: Florian Dold <florian@dold.me>
Date:   Fri, 25 Apr 2025 16:32:30 +0200

util: fix planchet derivation for refreshV2

Diffstat:
Mpackages/taler-wallet-core/src/crypto/cryptoImplementation.ts | 58+++++++++++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 53 insertions(+), 5 deletions(-)

diff --git a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts @@ -233,6 +233,10 @@ export interface TalerCryptoInterface { req: SetupRefreshPlanchetRequest, ): Promise<FreshCoinEncoded>; + setupRefreshPlanchetV2( + req: SetupRefreshPlanchetRequestV2, + ): Promise<FreshCoinEncoded>; + setupWithdrawalPlanchet( req: SetupWithdrawalPlanchetRequest, ): Promise<FreshCoinEncoded>; @@ -428,6 +432,11 @@ export const nullCrypto: TalerCryptoInterface = { ): Promise<FreshCoinEncoded> { throw new Error("Function not implemented."); }, + setupRefreshPlanchetV2: function ( + req: SetupRefreshPlanchetRequestV2, + ): Promise<FreshCoinEncoded> { + throw new Error("Function not implemented."); + }, rsaBlind: function (req: RsaBlindRequest): Promise<RsaBlindResponse> { throw new Error("Function not implemented."); }, @@ -557,6 +566,15 @@ export interface SetupRefreshPlanchetRequest { coinNumber: number; } +export interface SetupRefreshPlanchetRequestV2 { + /** + * Refresh planchet secret, derived from the + * secret signature made by the old coin. + */ + refreshPlanchetSecret: string; + coinNumber: number; +} + export interface SetupWithdrawalPlanchetRequest { secretSeed: string; coinNumber: number; @@ -840,6 +858,36 @@ export const nativeCryptoR: TalerCryptoInterfaceR = { }; }, + async setupRefreshPlanchetV2( + tci: TalerCryptoInterfaceR, + req: SetupRefreshPlanchetRequestV2, + ): Promise<FreshCoinEncoded> { + const planchetMasterSecret = decodeCrock(req.refreshPlanchetSecret); + + const coinPriv = kdfKw({ + ikm: planchetMasterSecret, + outputLength: 32, + salt: stringToBytes("coin"), + }); + + const bks = kdfKw({ + ikm: planchetMasterSecret, + outputLength: 32, + salt: stringToBytes("bks"), + }); + + const coinPrivEnc = encodeCrock(coinPriv); + const coinPubRes = await tci.eddsaGetPublic(tci, { + priv: coinPrivEnc, + }); + + return { + bks: encodeCrock(bks), + coinPriv: coinPrivEnc, + coinPub: coinPubRes.pub, + }; + }, + async setupWithdrawalPlanchet( tci: TalerCryptoInterfaceR, req: SetupWithdrawalPlanchetRequest, @@ -1539,7 +1587,7 @@ export const nativeCryptoR: TalerCryptoInterfaceR = { }); signatures.push(sig.sig); - const planchetSecretBytes = kdfKw({ + const planchetSecretsBytes = kdfKw({ outputLength: 32 * numCoins, salt: stringToBytes("refresh-planchet-secret"), ikm: decodeCrock(sig.sig), @@ -1549,20 +1597,20 @@ export const nativeCryptoR: TalerCryptoInterfaceR = { const denomSel = newCoinDenoms[j]; for (let k = 0; k < newCoinDenoms[j].count; k++) { const coinIndex = planchets.length; - const mySecret = planchetSecretBytes.slice( + const myPlanchetSecret = planchetSecretsBytes.slice( 32 * coinIndex, 32 * coinIndex + 32, ); - let fresh: FreshCoinEncoded = await tci.setupRefreshPlanchet(tci, { + let fresh: FreshCoinEncoded = await tci.setupRefreshPlanchetV2(tci, { coinNumber: coinIndex, - transferSecret: encodeCrock(mySecret), + refreshPlanchetSecret: encodeCrock(myPlanchetSecret), }); let newAc: AgeCommitmentProof | undefined = undefined; let newAch: HashCodeString | undefined = undefined; if (req.meltCoinAgeCommitmentProof) { newAc = await AgeRestriction.commitmentDerive( req.meltCoinAgeCommitmentProof, - mySecret, + myPlanchetSecret, ); newAch = AgeRestriction.hashCommitment(newAc.commitment); }