gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] branch master updated: implement IBAN validation


From: gnunet
Subject: [taler-wallet-core] branch master updated: implement IBAN validation
Date: Wed, 29 Mar 2023 18:50:11 +0200

This is an automated email from the git hooks/post-receive script.

dold pushed a commit to branch master
in repository wallet-core.

The following commit(s) were added to refs/heads/master by this push:
     new a17a08ae2 implement IBAN validation
a17a08ae2 is described below

commit a17a08ae287a46e837c9a691caebdd029a07dc62
Author: Florian Dold <florian@dold.me>
AuthorDate: Wed Mar 29 18:49:06 2023 +0200

    implement IBAN validation
---
 packages/taler-util/src/iban.test.ts               |  32 +++
 packages/taler-util/src/iban.ts                    | 296 +++++++++++++++++++++
 packages/taler-util/src/index.ts                   |   1 +
 packages/taler-util/src/wallet-types.ts            |  27 +-
 packages/taler-wallet-core/src/wallet-api-types.ts |  12 +
 packages/taler-wallet-core/src/wallet.ts           |  11 +
 6 files changed, 374 insertions(+), 5 deletions(-)

diff --git a/packages/taler-util/src/iban.test.ts 
b/packages/taler-util/src/iban.test.ts
new file mode 100644
index 000000000..69fb2dd75
--- /dev/null
+++ b/packages/taler-util/src/iban.test.ts
@@ -0,0 +1,32 @@
+/*
+ This file is part of GNU Taler
+ (C) 2023 Taler Systems S.A.
+
+ 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/>
+ */
+
+import test from "ava";
+import { generateIban, validateIban } from "./iban.js";
+
+test("iban validation", (t) => {
+  t.assert(validateIban("foo").type === "invalid");
+  t.assert(validateIban("NL71RABO9996666778").type === "valid");
+  t.assert(validateIban("NL71RABO9996666779").type === "invalid");
+});
+
+
+
+test("iban generation", (t) => {
+  let iban1 = generateIban("DE", 10);
+  console.log("generated IBAN", iban1);
+  t.assert(validateIban(iban1).type === "valid");
+});
diff --git a/packages/taler-util/src/iban.ts b/packages/taler-util/src/iban.ts
new file mode 100644
index 000000000..3a40e45a4
--- /dev/null
+++ b/packages/taler-util/src/iban.ts
@@ -0,0 +1,296 @@
+/*
+ This file is part of GNU Taler
+ (C) 2023 Taler Systems S.A.
+
+ 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/>
+ */
+
+/**
+ * IBAN validation.
+ *
+ * Currently only validates the checksum.
+ *
+ * It does not validate:
+ * - Country-specific length
+ * - Country-specific checksums
+ *
+ * The country list is also not complete.
+ *
+ * @author Florian Dold <dold@taler.net>
+ */
+
+export type IbanValidationResult =
+  | { type: "invalid" }
+  | {
+      type: "valid";
+      normalizedIban: string;
+    };
+
+export interface IbanCountryInfo {
+  name: string;
+  isSepa?: boolean;
+  length?: number;
+}
+
+/**
+ * Incomplete list, see https://www.swift.com/resource/iban-registry-pdf
+ */
+export const ibanCountryInfoTable: Record<string, IbanCountryInfo> = {
+  AE: { name: "U.A.E." },
+  AF: { name: "Afghanistan" },
+  AL: { name: "Albania" },
+  AM: { name: "Armenia" },
+  AN: { name: "Netherlands Antilles" },
+  AR: { name: "Argentina" },
+  AT: { name: "Austria" },
+  AU: { name: "Australia" },
+  AZ: { name: "Azerbaijan" },
+  BA: { name: "Bosnia and Herzegovina" },
+  BD: { name: "Bangladesh" },
+  BE: { name: "Belgium" },
+  BG: { name: "Bulgaria" },
+  BH: { name: "Bahrain" },
+  BN: { name: "Brunei Darussalam" },
+  BO: { name: "Bolivia" },
+  BR: { name: "Brazil" },
+  BT: { name: "Bhutan" },
+  BY: { name: "Belarus" },
+  BZ: { name: "Belize" },
+  CA: { name: "Canada" },
+  CG: { name: "Congo" },
+  CH: { name: "Switzerland" },
+  CI: { name: "Cote d'Ivoire" },
+  CL: { name: "Chile" },
+  CM: { name: "Cameroon" },
+  CN: { name: "People's Republic of China" },
+  CO: { name: "Colombia" },
+  CR: { name: "Costa Rica" },
+  CS: { name: "Serbia and Montenegro" },
+  CZ: { name: "Czech Republic" },
+  DE: { name: "Germany" },
+  DK: { name: "Denmark" },
+  DO: { name: "Dominican Republic" },
+  DZ: { name: "Algeria" },
+  EC: { name: "Ecuador" },
+  EE: { name: "Estonia" },
+  EG: { name: "Egypt" },
+  ER: { name: "Eritrea" },
+  ES: { name: "Spain" },
+  ET: { name: "Ethiopia" },
+  FI: { name: "Finland" },
+  FO: { name: "Faroe Islands" },
+  FR: { name: "France" },
+  GB: { name: "United Kingdom" },
+  GD: { name: "Caribbean" },
+  GE: { name: "Georgia" },
+  GL: { name: "Greenland" },
+  GR: { name: "Greece" },
+  GT: { name: "Guatemala" },
+  HK: { name: "Hong Kong S.A.R." },
+  HN: { name: "Honduras" },
+  HR: { name: "Croatia" },
+  HT: { name: "Haiti" },
+  HU: { name: "Hungary" },
+  ID: { name: "Indonesia" },
+  IE: { name: "Ireland" },
+  IL: { name: "Israel" },
+  IN: { name: "India" },
+  IQ: { name: "Iraq" },
+  IR: { name: "Iran" },
+  IS: { name: "Iceland" },
+  IT: { name: "Italy" },
+  JM: { name: "Jamaica" },
+  JO: { name: "Jordan" },
+  JP: { name: "Japan" },
+  KE: { name: "Kenya" },
+  KG: { name: "Kyrgyzstan" },
+  KH: { name: "Cambodia" },
+  KR: { name: "South Korea" },
+  KW: { name: "Kuwait" },
+  KZ: { name: "Kazakhstan" },
+  LA: { name: "Laos" },
+  LB: { name: "Lebanon" },
+  LI: { name: "Liechtenstein" },
+  LK: { name: "Sri Lanka" },
+  LT: { name: "Lithuania" },
+  LU: { name: "Luxembourg" },
+  LV: { name: "Latvia" },
+  LY: { name: "Libya" },
+  MA: { name: "Morocco" },
+  MC: { name: "Principality of Monaco" },
+  MD: { name: "Moldava" },
+  ME: { name: "Montenegro" },
+  MK: { name: "Former Yugoslav Republic of Macedonia" },
+  ML: { name: "Mali" },
+  MM: { name: "Myanmar" },
+  MN: { name: "Mongolia" },
+  MO: { name: "Macau S.A.R." },
+  MT: { name: "Malta" },
+  MV: { name: "Maldives" },
+  MX: { name: "Mexico" },
+  MY: { name: "Malaysia" },
+  NG: { name: "Nigeria" },
+  NI: { name: "Nicaragua" },
+  NL: { name: "Netherlands" },
+  NO: { name: "Norway" },
+  NP: { name: "Nepal" },
+  NZ: { name: "New Zealand" },
+  OM: { name: "Oman" },
+  PA: { name: "Panama" },
+  PE: { name: "Peru" },
+  PH: { name: "Philippines" },
+  PK: { name: "Islamic Republic of Pakistan" },
+  PL: { name: "Poland" },
+  PR: { name: "Puerto Rico" },
+  PT: { name: "Portugal" },
+  PY: { name: "Paraguay" },
+  QA: { name: "Qatar" },
+  RE: { name: "Reunion" },
+  RO: { name: "Romania" },
+  RS: { name: "Serbia" },
+  RU: { name: "Russia" },
+  RW: { name: "Rwanda" },
+  SA: { name: "Saudi Arabia" },
+  SE: { name: "Sweden" },
+  SG: { name: "Singapore" },
+  SI: { name: "Slovenia" },
+  SK: { name: "Slovak" },
+  SN: { name: "Senegal" },
+  SO: { name: "Somalia" },
+  SR: { name: "Suriname" },
+  SV: { name: "El Salvador" },
+  SY: { name: "Syria" },
+  TH: { name: "Thailand" },
+  TJ: { name: "Tajikistan" },
+  TM: { name: "Turkmenistan" },
+  TN: { name: "Tunisia" },
+  TR: { name: "Turkey" },
+  TT: { name: "Trinidad and Tobago" },
+  TW: { name: "Taiwan" },
+  TZ: { name: "Tanzania" },
+  UA: { name: "Ukraine" },
+  US: { name: "United States" },
+  UY: { name: "Uruguay" },
+  VA: { name: "Vatican" },
+  VE: { name: "Venezuela" },
+  VN: { name: "Viet Nam" },
+  YE: { name: "Yemen" },
+  ZA: { name: "South Africa" },
+  ZW: { name: "Zimbabwe" },
+};
+
+let ccZero = "0".charCodeAt(0);
+let ccNine = "9".charCodeAt(0);
+let ccA = "A".charCodeAt(0);
+let ccZ = "Z".charCodeAt(0);
+
+/**
+ * Append a IBAN digit(s) based on a char code.
+ */
+function appendDigit(digits: number[], cc: number): boolean {
+  if (cc >= ccZero && cc <= ccNine) {
+    digits.push(cc - ccZero);
+  } else if (cc >= ccA && cc <= ccZ) {
+    const n = cc - ccA + 10;
+    digits.push(Math.floor(n / 10) % 10);
+    digits.push(n % 10);
+  } else {
+    return false;
+  }
+  return true;
+}
+
+/**
+ * Compute MOD-97-10 as per ISO/IEC 7064:2003.
+ */
+function mod97(digits: number[]): number {
+  let i = 0;
+  let modAccum = 0;
+  while (i < digits.length) {
+    let n = 0;
+    while (n < 9 && i < digits.length) {
+      modAccum = modAccum * 10 + digits[i];
+      i++;
+      n++;
+    }
+    modAccum = modAccum % 97;
+  }
+  return modAccum;
+}
+
+export function validateIban(ibanString: string): IbanValidationResult {
+  let myIban = ibanString.toLocaleUpperCase().replace(" ", "");
+  let countryCode = myIban.substring(0, 2);
+  let countryInfo = ibanCountryInfoTable[countryCode];
+
+  if (!countryInfo) {
+    return {
+      type: "invalid",
+    };
+  }
+
+  let digits: number[] = [];
+
+  for (let i = 4; i < myIban.length; i++) {
+    const cc = myIban.charCodeAt(i);
+    if (!appendDigit(digits, cc)) {
+      return {
+        type: "invalid",
+      };
+    }
+  }
+
+  for (let i = 0; i < 4; i++) {
+    if (!appendDigit(digits, ibanString.charCodeAt(i))) {
+      return {
+        type: "invalid",
+      };
+    }
+  }
+
+  const rem = mod97(digits);
+  if (rem === 1) {
+    return {
+      type: "valid",
+      normalizedIban: myIban,
+    };
+  } else {
+    return {
+      type: "invalid",
+    };
+  }
+}
+
+export function generateIban(countryCode: string, length: number): string {
+  let ibanSuffix = "";
+  let digits: number[] = [];
+
+  for (let i = 0; i < length; i++) {
+    const cc = ccZero + (Math.floor(Math.random() * 100) % 10);
+    appendDigit(digits, cc)
+    ibanSuffix += String.fromCharCode(cc);
+  }
+
+  appendDigit(digits, countryCode.charCodeAt(0));
+  appendDigit(digits, countryCode.charCodeAt(1));
+
+  // Try using "00" as check digits
+  appendDigit(digits, ccZero);
+  appendDigit(digits, ccZero);
+
+  const requiredChecksum = 98 - mod97(digits);
+
+  const checkDigit1 = Math.floor(requiredChecksum / 10) % 10;
+  const checkDigit2 = requiredChecksum % 10;
+
+  return countryCode + checkDigit1 + checkDigit2 + ibanSuffix;
+}
diff --git a/packages/taler-util/src/index.ts b/packages/taler-util/src/index.ts
index cf4f545a4..1c882a82b 100644
--- a/packages/taler-util/src/index.ts
+++ b/packages/taler-util/src/index.ts
@@ -37,3 +37,4 @@ export * from "./contract-terms.js";
 export * from "./base64.js";
 export * from "./merchant-api-types.js";
 export * from "./errors.js";
+export * from "./iban.js";
diff --git a/packages/taler-util/src/wallet-types.ts 
b/packages/taler-util/src/wallet-types.ts
index 338124d08..b08b02ca3 100644
--- a/packages/taler-util/src/wallet-types.ts
+++ b/packages/taler-util/src/wallet-types.ts
@@ -1663,11 +1663,10 @@ export interface ResumeTransactionRequest {
   transactionId: string;
 }
 
-export const codecForResumeTransaction =
-  (): Codec<ResumeTransactionRequest> =>
-    buildCodecForObject<ResumeTransactionRequest>()
-      .property("transactionId", codecForString())
-      .build("ResumeTransactionRequest");
+export const codecForResumeTransaction = (): Codec<ResumeTransactionRequest> =>
+  buildCodecForObject<ResumeTransactionRequest>()
+    .property("transactionId", codecForString())
+    .build("ResumeTransactionRequest");
 
 export interface AbortTransactionRequest {
   transactionId: string;
@@ -2257,3 +2256,21 @@ export interface PayPeerInsufficientBalanceDetails {
     };
   };
 }
+
+export interface ValidateIbanRequest {
+  iban: string;
+}
+
+export const codecForValidateIbanRequest = (): Codec<ValidateIbanRequest> =>
+  buildCodecForObject<ValidateIbanRequest>()
+    .property("iban", codecForString())
+    .build("ValidateIbanRequest");
+
+export interface ValidateIbanResponse {
+  valid: boolean;
+}
+
+export const codecForValidateIbanResponse = (): Codec<ValidateIbanResponse> =>
+  buildCodecForObject<ValidateIbanResponse>()
+    .property("valid", codecForBoolean())
+    .build("ValidateIbanResponse");
diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts 
b/packages/taler-wallet-core/src/wallet-api-types.ts
index 84bad09fe..0b1968857 100644
--- a/packages/taler-wallet-core/src/wallet-api-types.ts
+++ b/packages/taler-wallet-core/src/wallet-api-types.ts
@@ -110,6 +110,8 @@ import {
   WithdrawFakebankRequest,
   WithdrawTestBalanceRequest,
   WithdrawUriInfoResponse,
+  ValidateIbanRequest,
+  ValidateIbanResponse,
 } from "@gnu-taler/taler-util";
 import { WalletContractData } from "./db.js";
 import {
@@ -197,6 +199,7 @@ export enum WalletApiOperation {
   Recycle = "recycle",
   SetDevMode = "setDevMode",
   ApplyDevExperiment = "applyDevExperiment",
+  ValidateIban = "validateIban",
 }
 
 // group: Initialization
@@ -683,6 +686,14 @@ export type ConfirmPeerPullDebitOp = {
   response: EmptyObject;
 };
 
+// group: Data Validation
+
+export type ValidateIbanOp = {
+  op: WalletApiOperation.ValidateIban;
+  request: ValidateIbanRequest;
+  response: ValidateIbanResponse;
+};
+
 // group: Database Management
 
 /**
@@ -937,6 +948,7 @@ export type WalletOperations = {
   [WalletApiOperation.Recycle]: RecycleOp;
   [WalletApiOperation.ApplyDevExperiment]: ApplyDevExperimentOp;
   [WalletApiOperation.SetDevMode]: SetDevModeOp;
+  [WalletApiOperation.ValidateIban]: ValidateIbanOp;
 };
 
 export type WalletCoreRequestType<
diff --git a/packages/taler-wallet-core/src/wallet.ts 
b/packages/taler-wallet-core/src/wallet.ts
index 6197f000e..363214e7b 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -109,6 +109,9 @@ import {
   WalletNotification,
   codecForSuspendTransaction,
   codecForResumeTransaction,
+  validateIban,
+  codecForValidateIbanRequest,
+  ValidateIbanResponse,
 } from "@gnu-taler/taler-util";
 import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js";
 import {
@@ -1043,6 +1046,14 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
       await runIntegrationTest(ws, req);
       return {};
     }
+    case WalletApiOperation.ValidateIban: {
+      const req = codecForValidateIbanRequest().decode(payload);
+      const valRes = validateIban(req.iban);
+      const resp: ValidateIbanResponse = {
+        valid: valRes.type === "valid",
+      };
+      return resp;
+    }
     case WalletApiOperation.TestPay: {
       const req = codecForTestPayArgs().decode(payload);
       return await testPay(ws, req);

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]