/* * This file is part of GNU Taler * (C) 2020 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 */ package net.taler.wallet.kotlin.crypto import net.taler.wallet.kotlin.Amount import net.taler.wallet.kotlin.Base32Crockford import net.taler.wallet.kotlin.Timestamp import net.taler.wallet.kotlin.crypto.Signature.PurposeBuilder import net.taler.wallet.kotlin.exchange.DenominationRecord import net.taler.wallet.kotlin.exchange.DenominationStatus.Unverified import net.taler.wallet.kotlin.exchange.DenominationStatus.VerifiedBad import net.taler.wallet.kotlin.exchange.WireFee import kotlin.random.Random import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue class SignatureTest { private val crypto = CryptoFactory.getCrypto() private val signature = Signature(crypto) private class PurposeBuilderVector(val purposeNum: Int, val chunks: List, val result: String) @Test fun testSignaturePurposeBuilder() { val vectors = listOf( PurposeBuilderVector(42, listOf("46D6FNS4VAFW0"), "000004000002M8CTCZBJ9PMZR0"), PurposeBuilderVector( 1375883724, listOf("7H2ENBHG1AVRD5QVKYCXKAZQQQGCSEAAZ4"), "000007AJ098WRF24XAQ302NQGTBFQ7WSV6NZFFF0SJWMNY8" ), PurposeBuilderVector(251873401, listOf("KVSRVCV6J7QGYR0"), "0000048F0D47K7QKHPSPD4FF1XG0"), PurposeBuilderVector( 2126178285, listOf("EJN927GRT1HNS0K53TBPWYB4", "QGSXW3NGHPJCVTBX3KHGW7GBC1SJWPK0", "XK7GA"), "00000BKYQBKYTX5AJ4F1HM33BJ16A7MQDSWP9F1KVR7B13D4SQMQT7731RF0PR3K5SD61V6F0M" ), PurposeBuilderVector( 871975602, listOf("JC26GG7M", "D5GER58NSW3MX3AB2EA0", "HVB81PFV0YYQ2170H77TQPZTB21T314P9B1N3VSXEHZ88VEPHW"), "00000E1KZ55B54R4D10F8TB0XGAHBKR79T6MP4WMHVB81PFV0YYQ2170H77TQPZTB21T314P9B1N3VSXEHZ88VEPHW" ), PurposeBuilderVector( 366986877, listOf("28", "48213TXMKY8H8EGRJ9XP00D9MHRHQG5N02W5GG0", "VR4CDW7WQ2FYQF3WBG60"), "00000B8NVZ37T4H20G8YQD4ZJ4A3M64JFDG03AD4E4DW1D80Q1C41QG8RVRFSE4ZXEY7RQ0C" ), PurposeBuilderVector( 1369171164, listOf( "SJTSKNX7W7CAP848GCESA09HQQQYQN50G1GY5AHPP12D4GMZJCXMCFPYECGEBF8", "J70SVFRH8WN1DP7DZ60BZW0", "275VB93D2MV6NN7V97QXC16VMX07JNCC5R3HBH1TSWTW5V72G1E66HP73ANWFXE6NG" ), "00000SJHKFJDSK5NK7BTFRERNCG8H0RXJM0K3FFFXFAA1031WAN3DC24T919Z4SV8RZDWWS0WPYS3GCXQW8MEAGPV3PZK05ZY08WQDD4DMAKCTPMZD4YZNG4VEKM0YANHGQ0E5E47B7KBGQCWA05RRT6RWDAQHZNRTP0" ), PurposeBuilderVector( 2115762114, listOf("Z7A3PQ7BV5G5D1P3F2EY9KN4GKVEG95KZD50", "SZ3PA4BSWF8T22TJMDZN5877Z9DQFC67K9E0"), "00000D3Y3FVW5YEM7DEEQPB0AT3C6Y4XWK7A917PX0JB7YTASZ3PA4BSWF8T22TJMDZN5877Z9DQFC67K9E0" ), PurposeBuilderVector( 1382722498, listOf("0WEEY7GRA8Q8ZWS2NRP0", "FQ5YY3ZWJ64014A0SPWMCE4B58", "G03C6M9PZ45D99G4M2HZJ"), "00000CAJDANW41RWXWF1GMHEHZSJ5BHCFQ5YY3ZWJ64014A0SPWMCE4B5A00DGTH6VWGNN560JGA7Y8" ), PurposeBuilderVector( 1557782497, listOf( "CEB9E7Q87X12R172RZ8DAHSGGS60", "M17JXQ290P209Q7EH759PW558FYZYTEHR40J18M81W", "B918DJVWTERCHS54S0G93DFPVK9TV383DMRJR43ZTFZBAE3CVSFG" ), "00000MTWV7FY2RWPJWFEGFT25G2E5HYGTN3K11JCM17JXQ290P209Q7EH759PW558FYZYTEHR40J18M81XD451PBFK9V1J74MK4214DNYVED7BCD0DPK2B0GFZ9ZXD9RDKF5Y" ), PurposeBuilderVector(1662106597, listOf("621Y24CA3ZCHJJ8"), "000004B326XYAC43W48RM7YS354G"), PurposeBuilderVector( 393436430, listOf( "6WMDVK6XKGEAX152ZM4SHQ77DCRQB4RYJX0MGCZFVVK3XTG", "8TKEY2F1PMAT396GC3E191182F34FXXR12MR7Z6WMG4S10GZX7RJG" ), "00000HGQEDEGWDS8VQ6DV70WNT2A5Z89K3EEETSHEP9HX5T190SYZQQ67VN4D9QF17GVA5D1MK861Q0MGGM17HJ7YYW0HAC3ZKEA82CGG8FYKW98" ), PurposeBuilderVector( 1616746709, listOf("ZQT1F901YWFDX6XZZB6R8PSQ1QJANCPF63T8RE09HRK2XQ7EE2K0F74GERH4GQ0"), "00000BV0BPCDBZFM2YJ03XRYVTDVZYPDGHDKE3F4NASCYC7MHGW0K3H65VEEWW560YE90XH291E0" ), PurposeBuilderVector( 691857007, listOf("5JT5DM7KWVW7A7P764NTG0FJDWEQTQAGB253J547GXWX93204NM0"), "00000A197KK6YB5MAV8F7SQREMFCEC9BN00Z4VRXFNEN0P4A74A8F1VSTJ6409B8" ), PurposeBuilderVector( 582885064, listOf("W5P2HP14MCNSKX4CZ4FZ0BDPSX2SV7MCE45V8PF7DW9EXAGK3TC0"), "00000A12QRFCHRBC53C298SBK7T8SY8ZY0PVDKT5KPF8RW8BPHCYEVRJXTN167MR" ), PurposeBuilderVector( 480383332, listOf( "49DZJY0K1WJFDK7P86Q1R2F150S50JQR8HQW059HBG9CR615N4V82", "D0XXQKX9BB0HVJK34TKCZJ1E2JQKF06EQVXRRD2ZKX4QGXG", "99JY6SS2HJ1J29GS1793CGE5BKSG" ), "00000NRWM88P88JVZ5W163S4YV6FCGDE3G4Y2A1JA15FGH3FR0AK2Q0JSGC2BA9PG5M3QPYFN5DC27EACCKADKY85RAAYDW0STZFQ31MBYFMJY3P99JY6SS2HJ1J29GS1793CGE5BKSG" ), PurposeBuilderVector( 877078324, listOf("RC", "K1PKX78BPGNQSSZCVRB3B6PJYR", "74"), "000006HM8WKK9GWRDMZ9T2XM5DYEFV6Y2RTSNMQP74" ), PurposeBuilderVector( 25488155, listOf( "FNG58X0W2A4HV4ZY5TKS73RGNHA8KZ3B4WWDJS2V1B27F3G6G08FNHM8NYBZ45Q8", "NQGNDPDNW4G9AEHXB1BKXQXK84" ), "00000G01GKNHPZB0AHT1R4M93P9ZWBN7JE7H1B2MH7Y6P9SRV5J5P2P4EY70D00GZB38HBWQY8BEHBF1AVCVBR90JMX3TP2Q7VFV6G8" ), PurposeBuilderVector( 749389285, listOf( "FVD8X18A3S2KHJ7143TCEHE3K083DRBG2CA0D766NMRVHX7MSN78NSYHZSWNH9V2DM", "1TSPZ4VBKEBKCQEB2673H2FJHCZ6M19R2691NY79A0", "P9VW044VY2TGKNNF4ZWZ1S2ACM4VJYCWP7ZGWRKR1264ZS2CQZGSXZ775AC41X5778" ), "00000X1CNB2YAZPTHT2GM7J5734E287MRX2W760G6VGQ04RM0TECDB9HQ3TF9KAEHBKX3ZKSB2KP4V8EPDQS6TWVJWV5VJRHHRW8KWMB7SN0AE0HJ8DFHTAGP9VW044VY2TGKNNF4ZWZ1S2ACM4VJYCWP7ZGWRKR1264ZS2CQZGSXZ775AC41X5778" ), PurposeBuilderVector( 434249902, listOf( "VFERBNSH7X70", "T5DWNBWMF3BPAGWSESKF7WGTTS3JHVKAAR3DR", "SMCA0184DN3WE934BGFE4MCE14279NYCFF0PJDQ20JRHFZXSGXE6CP1BSMGR479HAC" ), "00000KRSW8GAXPYXGQBK2FTET5DWNBWMF3BPAGWSESKF7WGTTS3JHVKAAR3DSK8RM02G8VA7RWJ68Q0YW98RW284EKBWRYY1D4VE415H2ZZVK1TWCSC2QK91G8EK2MR" ), PurposeBuilderVector( 1710327887, listOf("S64XB965NEAQQ5287XHNT126BW"), "00000635Y644ZJC9TPJCBAWNFEA4GFV3BM24CQR" ), PurposeBuilderVector( 1247371119, listOf("CSXX5W59E3S03412M29J0HG0R67BW4B3X946GS2T0TVYPH9HFP9FC0M7", "931KSGS0ZJG4A79BT55G0Q4Q"), "00000EJAB5FPYSKVTBRAJW7J0682584K41301GCEQR8P7TJ8D1J5M1NQXD2K2ZCJYR18EJ637K1J1Z508MEJQMAB01E9E" ), PurposeBuilderVector( 2024386767, listOf( "R2WZA0FKD57D418NB2KK84PJKHAVPEC6219X2KQJ78JFB76F0R", "CNYD5TAC7Y3N58CN6VK9F7NBKD2NJJK3VJ91CZKBFZWDVVZB28", "G7WQJVXXYT4JHA1NK22RXS45BM0W5H5JD7JNX3PWD2WZ27Y5Y1FB4" ), "00000SVRN6RCZG5SYM0Z6TAET82HAP576G9D572NQCWRC42KT57F4EH4YPECY1K5FK9EJK1ZGX9A359PWTBSXAWV8NCMMRYWJ8B7WTVZZ3EYZTRJG7WQJVXXYT4JHA1NK22RXS45BM0W5H5JD7JNX3PWD2WZ27Y5Y1FB4" ), PurposeBuilderVector(1182512118, listOf("KH32THSNNZGJPC0"), "000004A6FESZD7265N3KBBZ15CR0"), PurposeBuilderVector( 2029783771, listOf("N7468QH02K49BTD16S1F6FB4NKNXB0Q410"), "000007BRZG5DQAE8CHF20568JQMT2DJ2YCYP9B7BTP1E820" ), PurposeBuilderVector( 1111140596, listOf("6BN45PA2RCVRR8PJ6XKRJ9ZER2BR3MY9RQ290FADAG"), "000008J27AMF8CQA8BCM5GSQHGHD4DV7H4KYXG4QG79WKHE4J0YMTN0" ) ) for (v in vectors) { val builder = PurposeBuilder(v.purposeNum) for (chunk in v.chunks) builder.put(Base32Crockford.decode(chunk)) val encodedBytes = Base32Crockford.encode(builder.build()) assertEquals(v.result, encodedBytes) } } private class PaymentSignatureVector(val publicKey: String, val hash: String, val signature: String) @Test fun testVerifyPaymentSignature() { val vectors = listOf( PaymentSignatureVector( "8GSJZ649T2PXMKZC01Y4ANNBE7MF14QVK9SQEC4E46ZHKCVG8AS0", "CW96WR74JS8T53EC8GKSGD49QKH4ZNFTZXDAWMMV5GJ1E4BM6B8GPN5NVHDJ8ZVXNCW7Q4WBYCV61HCA3PZC2YJD850DT29RHHN7ESR", "JSNG99MX5W4AS7AEA8D4ADCHYTHFER0GX1N064E1XX48N513AXAEHDTG8ZT7ANWQK5HGCAXGEWN7TCBTYVG3RDPBTAS5HEP608KQ40R" ), PaymentSignatureVector( "6WC3MPYM5XKPKRA2Z6SYB81CPFV3E7EC6S2GVE095X8XH63QTZCG", "Z6H76JXPJFP3JBGSF54XBF0BVXDJ0CJBK4YT9GVR1AT916ZD57KP53YZN5G67A4YN95WGMZKQW7744483P5JDF06B6S7TMK195QGP20", "T2Y4KJJPZ0F2DMNF5S81V042T20VHB5VRXQYX4RF8KRH6H2Z4JRBD05CCDJ6C625MHM5FQET00RDX2NF5QX63S9YDXEP0710VBYHY10" ), PaymentSignatureVector( "2X3PSPT7D6TEM97R98C0DHZREFVAVA3XTH11D5A2Z2K7GBKQ7AEG", "JSNG99MX5W4AS7AEA8D4ADCHYTHFER0GX1N064E1XX48N513AXAEHDTG8ZT7ANWQK5HGCAXGEWN7TCBTYVG3RDPBTAS5HEP608KQ40R", "W0783H1KZ6GX58T5ZEH5VYMTXP7P7EA3KBKQ4Y8CN8M20GY8RNA4RX1AZG6TQ70NR4XG4EZ9D606P4RDAARD2SXTKA90BJMN9VEAC00" ) ) for (v in vectors) { // verification succeeds as expected assertTrue(signature.verifyPayment(v.signature, v.hash, v.publicKey)) // verification fails which different signature val size = Base32Crockford.calculateDecodedDataLength(v.signature.length) val sig = Base32Crockford.encode(Random.nextBytes(size)) assertFalse(signature.verifyPayment(sig, v.hash, v.publicKey)) // verification fails which different hash val hash = Base32Crockford.encode(Random.nextBytes(64)) assertFalse(signature.verifyPayment(v.signature, hash, v.publicKey)) // verification fails which different public key val publicKey = Base32Crockford.encode(Random.nextBytes(32)) assertFalse(signature.verifyPayment(v.signature, v.hash, publicKey)) } } private class WireFeeSignatureVector(val wireFee: WireFee, val masterPub: String) @Test fun testVerifyWireFeeSignature() { val type = "x-taler-bank" val vectors = listOf( WireFeeSignatureVector( WireFee( wireFee = Amount("TESTKUDOS", 0, 1000000), closingFee = Amount("TESTKUDOS", 0, 1000000), startStamp = Timestamp(1609470000000), endStamp = Timestamp(1641006000000), signature = "C77EZ56ZT4ACNPGP3PX881S42413N37NJVTRPNMM9BWRVGQBK2C157HHRF61RMYPAWW6QVWV5RVKEVBD3XAVJFXAEBM2HPNS5AMPY18" ), "Y8JGNCPMM7XTF44FT7V1JRNNJQ6F4R7DZPXAKYYCJZJVEX2C45QG" ), WireFeeSignatureVector( WireFee( wireFee = Amount("TESTKUDOS", 0, 1000000), closingFee = Amount("TESTKUDOS", 0, 1000000), startStamp = Timestamp(1577847600000), endStamp = Timestamp(1609470000000), signature = "4F87Z12WQM7JXEKPRPGFZVGCZPNN6Q9RVP2NA0PZ57CYQP4QXV38EQW59X3K5WAQN82BX95X57775ZJGA5EB3VA5GV9S3JBA3RGX41G" ), "0BJ4SX13W4Q64QDDSRBVTYSWT8C0X2HVB61QYSZM1B1W9JVCE0C0" ), WireFeeSignatureVector( WireFee( wireFee = Amount("TESTKUDOS", 0, 1000000), closingFee = Amount("TESTKUDOS", 0, 1000000), startStamp = Timestamp(1672542000000), endStamp = Timestamp(1704078000000), signature = "9YWES9YPR2W5ACCYE4ZE4S3PE62CVX01AXXSC9KT9PZ6B52D5GSBP4DG95Y024EDE5HFGP6FEZVPKQM8PA6J9WD2NXW3QBA34MEV61R" ), "FWPD4E312RB8XZ22FGHCZGV2YPT7AX02XVRZEC7F53QKZJHY7H50" ) ) for (v in vectors) { // verification succeeds as expected assertTrue(signature.verifyWireFee(type, v.wireFee, v.masterPub)) // different type fails verification assertFalse(signature.verifyWireFee("foo", v.wireFee, v.masterPub)) // different WireFee wireFee fails verification var wireFee = v.wireFee.copy(wireFee = Amount("TESTKUDOS", 0, 100)) assertFalse(signature.verifyWireFee(type, wireFee, v.masterPub)) // different WireFee closingFee fails verification wireFee = v.wireFee.copy(closingFee = Amount("TESTKUDOS", 0, 100)) assertFalse(signature.verifyWireFee(type, wireFee, v.masterPub)) // different WireFee startStamp fails verification wireFee = v.wireFee.copy(startStamp = Timestamp(v.wireFee.startStamp.ms + 1000)) assertFalse(signature.verifyWireFee(type, wireFee, v.masterPub)) // different WireFee endStamp fails verification wireFee = v.wireFee.copy(endStamp = Timestamp(v.wireFee.endStamp.ms + 1000)) assertFalse(signature.verifyWireFee(type, wireFee, v.masterPub)) // different WireFee signature fails verification val size = Base32Crockford.calculateDecodedDataLength(v.wireFee.signature.length) wireFee = v.wireFee.copy(signature = Base32Crockford.encode(Random.nextBytes(size))) assertFalse(signature.verifyWireFee(type, wireFee, v.masterPub)) // startStamp changes below one second don't affect verification wireFee = v.wireFee.copy(startStamp = Timestamp(v.wireFee.startStamp.ms + 999)) assertTrue(signature.verifyWireFee(type, wireFee, v.masterPub)) // startStamp changes below one second don't affect verification wireFee = v.wireFee.copy(endStamp = Timestamp(v.wireFee.endStamp.ms + 999)) assertTrue(signature.verifyWireFee(type, wireFee, v.masterPub)) // different masterPub fails verification val masterPub = Base32Crockford.encode(Random.nextBytes(32)) assertFalse(signature.verifyWireFee(type, v.wireFee, masterPub)) } } private class DenominationRecordSignatureVector(val denominationRecord: DenominationRecord, val masterPub: String) @Test fun testVerifyDenominationRecordSignature() { val vectors = listOf( DenominationRecordSignatureVector( DenominationRecord( value = Amount("TESTKUDOS", 4, 0), denomPub = "020000XX26WN5X7ECKERVGBWTFM3KJ7AT0N8T7RQB7W7G4Q5K0W1BT8QFQBMMTR925TC6RX4QGVVVXH2ZMJVWDRR58BRXNDCFBZTH7RNS1KVWZQGEWME1D6QX79R0V6V9S0NC9H8YP0W6MJD7YSV5VQZWCR1JXRTSS0HPHDV4V6AVX34TSMHGRDQXZYVTEBGVWNF8TNR2P5296TCZR0G2001", denomPubHash = "MY54RXQ1WTZPFD3VZ4QJH6RHFPTYE78Y4DQ3GANTWK2Z0SQ99AK90K5H0P419EY4QWV6S4Q6QG52HYFCVRCPEQNG22RM3E7XW2YFJ9R", feeWithdraw = Amount("TESTKUDOS", 0, 3000000), feeDeposit = Amount("TESTKUDOS", 0, 3000000), feeRefresh = Amount("TESTKUDOS", 0, 4000000), feeRefund = Amount("TESTKUDOS", 0, 2000000), stampStart = Timestamp(1593449948000), stampExpireWithdraw = Timestamp(1594054748000), stampExpireLegal = Timestamp(1688057948000), stampExpireDeposit = Timestamp(1656521948000), masterSig = "2PEF4EHTKE2R89KHQBZ6NAQW34YEMAP044DTJXJ9TFX224YW07BB2X1VHEYH425N2RNDZS9XFEAQK54GV6Q6CTNSE1Z038TZ4V3MM1R", status = Unverified, isOffered = false, isRevoked = false, exchangeBaseUrl = "example.org" ), "C5K3YXPHSDVYPCB79D5AC4FYCKQ9D0DN3N3MBA5D54BG42SX0ZRG" ), DenominationRecordSignatureVector( DenominationRecord( value = Amount("TESTKUDOS", 0, 10000000), denomPub = "020000XWWS2DFXM1G8YR6NXVF07R0DH7GRE1J7ZC2YENN7Q60ZHX9S7FEVQDBFP1041DN0GFASZR6A7RSJ3EYRV1YD5ACAZRDQNH5P5KEBTSK5XA76YYWTW5VA7N1V1VRVF0CF7VZ8GSJNQ91FSJGNKGR82DB7H82TPAGMR1B5DG5SY2WP3NB5YJD90H64E09C0YC8FCCWRGC09HFNPG2001", denomPubHash = "J73HZJTR76GRZKBEKQC4X289056V5RMY4JS932XM8BAENQ83YAF9W2WYKRBN87TN8ENXP61JC7HMC7PYK9MZ8S289FCD07EM95AJGMR", feeWithdraw = Amount("TESTKUDOS", 0, 1000000), feeDeposit = Amount("TESTKUDOS", 0, 1000000), feeRefresh = Amount("TESTKUDOS", 0, 3000000), feeRefund = Amount("TESTKUDOS", 0, 1000000), stampStart = Timestamp(1593450307000), stampExpireWithdraw = Timestamp(1594055107000), stampExpireLegal = Timestamp(1688058307000), stampExpireDeposit = Timestamp(1656522307000), masterSig = "HPBAY19C1B5H3FAYWKRQFS8VC693658SNSK3BB304BNAG950BS881GMVPVN0YBG67K9J9E4A9BFN7VAQEY1D5G6YAGVPFQWX0GRQ62G", status = Unverified, isOffered = false, isRevoked = false, exchangeBaseUrl = "example.org" ), "NF8G14QFVWJSQPNFC3M2JKNMGJESS8ZWZX9V43PG40YXWRWA88VG" ) ) for (v in vectors) { // verification succeeds as expected assertTrue(signature.verifyDenominationRecord(v.denominationRecord, v.masterPub)) // different masterPub fails verification val masterPub = Base32Crockford.encode(Random.nextBytes(32)) assertFalse(signature.verifyDenominationRecord(v.denominationRecord, masterPub)) // different value fails verification val value = v.denominationRecord.value + Amount.min(v.denominationRecord.value.currency) assertFalse( signature.verifyDenominationRecord( v.denominationRecord.copy(value = value), v.masterPub ) ) // different denomPubHash fails verification val calculatedDenomPubHash = crypto.sha512(Base32Crockford.decode(v.denominationRecord.denomPub)) assertEquals(v.denominationRecord.denomPubHash, Base32Crockford.encode(calculatedDenomPubHash)) val denomPubHash = Base32Crockford.encode(Random.nextBytes(calculatedDenomPubHash.size)) assertFalse( signature.verifyDenominationRecord( v.denominationRecord.copy(denomPubHash = denomPubHash), v.masterPub ) ) // different feeWithdraw fails verification val feeWithdraw = v.denominationRecord.feeWithdraw + Amount.min(v.denominationRecord.feeWithdraw.currency) assertFalse( signature.verifyDenominationRecord( v.denominationRecord.copy(feeWithdraw = feeWithdraw), v.masterPub ) ) // different feeDeposit fails verification val feeDeposit = v.denominationRecord.feeDeposit + Amount.min(v.denominationRecord.feeDeposit.currency) assertFalse( signature.verifyDenominationRecord( v.denominationRecord.copy(feeDeposit = feeDeposit), v.masterPub ) ) // different feeRefresh fails verification val feeRefresh = v.denominationRecord.feeRefresh + Amount.min(v.denominationRecord.feeRefresh.currency) assertFalse( signature.verifyDenominationRecord( v.denominationRecord.copy(feeRefresh = feeRefresh), v.masterPub ) ) // different feeRefund fails verification val feeRefund = v.denominationRecord.feeRefund + Amount.min(v.denominationRecord.feeRefund.currency) assertFalse( signature.verifyDenominationRecord( v.denominationRecord.copy(feeRefund = feeRefund), v.masterPub ) ) // different stampStart fails verification val stampStart = Timestamp(v.denominationRecord.stampStart.ms + 1000) assertFalse( signature.verifyDenominationRecord( v.denominationRecord.copy(stampStart = stampStart), v.masterPub ) ) // different stampExpireWithdraw fails verification val stampExpireWithdraw = Timestamp(v.denominationRecord.stampExpireWithdraw.ms + 1000) assertFalse( signature.verifyDenominationRecord( v.denominationRecord.copy(stampExpireWithdraw = stampExpireWithdraw), v.masterPub ) ) // different stampExpireLegal fails verification val stampExpireLegal = Timestamp(v.denominationRecord.stampExpireLegal.ms + 1000) assertFalse( signature.verifyDenominationRecord( v.denominationRecord.copy(stampExpireLegal = stampExpireLegal), v.masterPub ) ) // different stampExpireDeposit fails verification val stampExpireDeposit = Timestamp(v.denominationRecord.stampExpireDeposit.ms + 1000) assertFalse( signature.verifyDenominationRecord( v.denominationRecord.copy(stampExpireDeposit = stampExpireDeposit), v.masterPub ) ) // different masterPub fails verification val size = Base32Crockford.calculateDecodedDataLength(v.denominationRecord.masterSig.length) val masterSig = Base32Crockford.encode(Random.nextBytes(size)) assertFalse( signature.verifyDenominationRecord( v.denominationRecord.copy(masterSig = masterSig), v.masterPub ) ) // different status does not affect verification assertTrue( signature.verifyDenominationRecord( v.denominationRecord.copy(status = VerifiedBad), v.masterPub ) ) // different exchangeBaseUrl does not affect verification assertTrue( signature.verifyDenominationRecord( v.denominationRecord.copy(exchangeBaseUrl = "foo.bar"), v.masterPub ) ) } } private class WireAccountSignatureVector(val paytoUri: String, val signature: String, val masterPub: String) @Test fun testVerifyWireAccount() { val paytoUri = "payto://x-taler-bank/localhost/Exchange" val vectors = listOf( WireAccountSignatureVector( paytoUri, "7THNYN5G12GXZABHP187XJZ3ACTDKAWGHWYM2ERA1VGE4JMGPADXT37ZM8D4DVAQTSP8CR61VAD4ZZSWKRPP5KQ12JCTVHCKDD3KA3R", "QPJSEA4SM8E67106C6MN2TMG4308J20C1T0D411WED1FJ00VF8ZG" ), WireAccountSignatureVector( paytoUri, "6JJDJ0HG3AGRTEY1FFGHR89367GPE4FS7TC8Z26N34PHFAMSRXQ4FA7P96CDS6625SRNAN4DY1NK4TNBQXKRXQ9QR82HEVCB2FF1E18", "8QBE0SRR84GWJ1FX2QCGGNRZTWA2FCV6YQC98Q26DRRJ0QBQE930" ), WireAccountSignatureVector( paytoUri, "X5EYQJ388P8AH1PPYD2GFQ9Y1NGA7HV0TXAW6GJ7C0D6R6GAY0059HCDBE98TKJJT6MB1S660FV13DV46JDKJ622MR961XVGP6DG618", "802M037TN8GHBXEGBPC7J41HJC06TWBWXYH36AYQHRJ7NVHJBQQ0" ) ) for (v in vectors) { // verification succeeds as expected assertTrue(signature.verifyWireAccount(v.paytoUri, v.signature, v.masterPub)) // different paytoUri fails verification assertFalse(signature.verifyWireAccount("foo", v.signature, v.masterPub)) // different paytoUri fails verification val size = Base32Crockford.calculateDecodedDataLength(v.signature.length) val sig = Base32Crockford.encode(Random.nextBytes(size)) assertFalse(signature.verifyWireAccount(v.paytoUri, sig, v.masterPub)) // different paytoUri fails verification val masterPub = Base32Crockford.encode(Random.nextBytes(32)) assertFalse(signature.verifyWireAccount(v.paytoUri, sig, masterPub)) } } }