Skip to content

Commit 0f639ce

Browse files
Prevent Signature swapping within our APIs
Since the updated Schnorr API makes use of the same Signature class as ECDSA, we need to make sure signature swapping at our API layer is caught and rejected. This isn't a cryptographic mitigation. It is not meant to guard against algorithm confusion attacks. This is only intended to prevent a trivial kind of developer mistake.
1 parent 6fd3b48 commit 0f639ce

File tree

7 files changed

+84
-8
lines changed

7 files changed

+84
-8
lines changed

src/Crypto/Signature/SchnorrSigner.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Exception;
77
use GMP;
88
use InvalidArgumentException;
9+
use Mdanter\Ecc\Exception\IncorrectAlgorithmException;
910
use Mdanter\Ecc\Util\BinaryString;
1011
use Mdanter\Ecc\Crypto\Key\{
1112
PrivateKeyInterface,
@@ -49,7 +50,7 @@ public function signWithKey(
4950
// Deconstruct as a Signature object:
5051
$r = gmp_init(BinaryString::substring($results['signature'], 0, $l), 16);
5152
$s = gmp_init(BinaryString::substring($results['signature'], $l), 16);
52-
return new Signature($r, $s);
53+
return new Signature($r, $s, Signature::TYPE_SCHNORR);
5354
}
5455

5556
/**
@@ -63,6 +64,9 @@ public function verifyWithKey(
6364
Signature $signature,
6465
string $message
6566
): bool {
67+
if (!(hash_equals(Signature::TYPE_SCHNORR, $signature->getSignatureType()))) {
68+
throw new IncorrectAlgorithmException('This is not a Schnorr signature');
69+
}
6670
$ptX = gmp_strval($key->getPoint()->getX(), 16);
6771
$x = str_pad($ptX, 64, '0', STR_PAD_LEFT);
6872
$serialized = $this->formatSignature($key, $signature);

src/Crypto/Signature/Signature.php

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
namespace Mdanter\Ecc\Crypto\Signature;
55

6+
use GMP;
7+
68
/**
79
* *********************************************************************
810
* Copyright (C) 2012 Matyas Danter
@@ -32,34 +34,45 @@
3234
*/
3335
class Signature implements SignatureInterface
3436
{
37+
const TYPE_ECDSA = 'ecdsa';
38+
39+
const TYPE_SCHNORR = 'schnorr';
40+
3541
/**
36-
* @var \GMP
42+
* @var GMP
3743
*/
3844
private $r;
3945

4046
/**
4147
*
42-
* @var \GMP
48+
* @var GMP
4349
*/
4450
private $s;
4551

52+
/**
53+
* @var string $sigType
54+
*/
55+
private $sigType;
56+
4657
/**
4758
* Initialize a new instance with values
4859
*
49-
* @param \GMP $r
50-
* @param \GMP $s
60+
* @param GMP $r
61+
* @param GMP $s
62+
* @param string $signatureType "ecdsa" or "schnorr"
5163
*/
52-
public function __construct(\GMP $r, \GMP $s)
64+
public function __construct(GMP $r, GMP $s, string $signatureType = self::TYPE_ECDSA)
5365
{
5466
$this->r = $r;
5567
$this->s = $s;
68+
$this->sigType = $signatureType;
5669
}
5770

5871
/**
5972
* {@inheritDoc}
6073
* @see SignatureInterface::getR
6174
*/
62-
public function getR(): \GMP
75+
public function getR(): GMP
6376
{
6477
return $this->r;
6578
}
@@ -68,8 +81,16 @@ public function getR(): \GMP
6881
* {@inheritDoc}
6982
* @see SignatureInterface::getS
7083
*/
71-
public function getS(): \GMP
84+
public function getS(): GMP
7285
{
7386
return $this->s;
7487
}
88+
89+
/**
90+
* @return string
91+
*/
92+
public function getSignatureType(): string
93+
{
94+
return $this->sigType;
95+
}
7596
}

src/Crypto/Signature/SignatureInterface.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,11 @@ public function getR(): \GMP;
4545
* @return \GMP
4646
*/
4747
public function getS(): \GMP;
48+
49+
/**
50+
* Returns "ecdsa" or "schnorr" depending on the signature type.
51+
*
52+
* @return string
53+
*/
54+
public function getSignatureType(): string;
4855
}

src/Crypto/Signature/Signer.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Mdanter\Ecc\Curves\NamedCurveFp;
1111
use Mdanter\Ecc\Curves\NistCurve;
1212
use Mdanter\Ecc\Curves\SecgCurve;
13+
use Mdanter\Ecc\Exception\IncorrectAlgorithmException;
1314
use Mdanter\Ecc\Math\ConstantTimeMath;
1415
use Mdanter\Ecc\Math\GmpMathInterface;
1516
use Mdanter\Ecc\Crypto\Key\PrivateKeyInterface;
@@ -211,6 +212,11 @@ public function verify(PublicKeyInterface $key, SignatureInterface $signature, G
211212
$r = $signature->getR();
212213
$s = $signature->getS();
213214

215+
// Make sure this isn't a Schnorr signature:
216+
if (!(hash_equals(Signature::TYPE_ECDSA, $signature->getSignatureType()))) {
217+
throw new IncorrectAlgorithmException('This is not an ECDSA signature');
218+
}
219+
214220
$math = $this->adapter;
215221
/** @var GMP $one */
216222
$one = gmp_init(1, 10);
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace Mdanter\Ecc\Exception;
4+
5+
class IncorrectAlgorithmException extends \RuntimeException
6+
{
7+
8+
}

tests/unit/Crypto/Signature/SchnorrSignerTest.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,14 @@
77
use Exception;
88
use Mdanter\Ecc\Crypto\Key\PrivateKey;
99
use Mdanter\Ecc\Crypto\Signature\SchnorrSigner;
10+
use Mdanter\Ecc\Crypto\Signature\Signature;
11+
use Mdanter\Ecc\Crypto\Signature\Signer;
12+
use Mdanter\Ecc\Crypto\Signature\SignHasher;
1013
use Mdanter\Ecc\Curves\SecureCurveFactory;
14+
use Mdanter\Ecc\Exception\IncorrectAlgorithmException;
1115
use Mdanter\Ecc\Exception\InsecureCurveException;
1216
use Mdanter\Ecc\Math\ConstantTimeMath;
17+
use Mdanter\Ecc\Math\GmpMath;
1318
use Mdanter\Ecc\Tests\AbstractTestCase;
1419

1520
/**
@@ -99,6 +104,30 @@ public function testSchnorrVerificationAndSigning(
99104

100105
// Ensure the same verification result occurs:
101106
self::assertSame($signer->formatSignature($pkObject, $signResult2), $signResult['signature']);
107+
108+
// First, we make a fake "ECDSA" signature out of this Schnorr signature and ensure it's not accepted:
109+
$ecdsaSig = new Signature($signResult2->getR(), $signResult2->getS());
110+
111+
$thrown = false;
112+
try {
113+
$signer->verifyWithKey($pkObject, $ecdsaSig, $message);
114+
} catch (IncorrectAlgorithmException $ex) {
115+
$thrown = true;
116+
}
117+
self::assertTrue($thrown, 'ECDSA / Schnorr signatures can be swapped');
118+
119+
// Next, we try the inverse attack: Feeding a Schnorr sig into the ECDSA class:
120+
$math = new GmpMath();
121+
$ecdsaSigner = new Signer($math, true);
122+
$hash = (new SignHasher('sha256', $math))->makeHash($message, $generator);
123+
124+
$thrown = false;
125+
try {
126+
$ecdsaSigner->verify($pkObject, $signResult2, $hash);
127+
} catch (IncorrectAlgorithmException $ex) {
128+
$thrown = true;
129+
}
130+
self::assertTrue($thrown, 'ECDSA / Schnorr signatures can be swapped');
102131
}
103132

104133
/**

tests/unit/Crypto/Signature/SignatureTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ public function testInstance()
1515
$signature = new Signature($r, $s);
1616
$this->assertSame($r, $signature->getR());
1717
$this->assertSame($s, $signature->getS());
18+
$this->assertSame(Signature::TYPE_ECDSA, $signature->getSignatureType());
1819
}
1920
}

0 commit comments

Comments
 (0)