Skip to content

Commit 83ea466

Browse files
authored
✨ P256 s normalized (#1363)
1 parent dcf23b8 commit 83ea466

4 files changed

Lines changed: 48 additions & 0 deletions

File tree

docs/utils/p256.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,14 @@ Includes the malleability check.
8989

9090
## Other Operations
9191

92+
### normalized(bytes32)
93+
94+
```solidity
95+
function normalized(bytes32 s) internal pure returns (bytes32 result)
96+
```
97+
98+
Returns `s` normalized to the lower half of the curve.
99+
92100
### tryDecodePoint(bytes)
93101

94102
```solidity

src/utils/P256.sol

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,14 @@ library P256 {
112112
/* OTHER OPERATIONS */
113113
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
114114

115+
/// @dev Returns `s` normalized to the lower half of the curve.
116+
function normalized(bytes32 s) internal pure returns (bytes32 result) {
117+
/// @solidity memory-safe-assembly
118+
assembly {
119+
result := xor(s, mul(xor(sub(N, s), s), gt(s, _HALF_N)))
120+
}
121+
}
122+
115123
/// @dev Helper function for `abi.decode(encoded, (bytes32, bytes32))`.
116124
/// If `encoded.length < 64`, `(x, y)` will be `(0, 0)`, which is an invalid point.
117125
function tryDecodePoint(bytes memory encoded) internal pure returns (bytes32 x, bytes32 y) {

test/P256.t.sol

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,32 @@ contract P256Test is P256VerifierEtcher {
248248
assertEq(xDecoded, x);
249249
assertEq(yDecoded, y);
250250
}
251+
252+
function check_P256Normalized(uint256 s) public pure {
253+
uint256 n = uint256(P256.N);
254+
unchecked {
255+
uint256 expected = s > (n / 2) ? n - s : s;
256+
assert(uint256(P256.normalized(bytes32(s))) == expected);
257+
}
258+
}
259+
260+
function testP256Normalized(uint256 privateKey, bytes32 hash) public {
261+
while (privateKey == 0 || privateKey >= P256.N) {
262+
privateKey = uint256(keccak256(abi.encode(privateKey)));
263+
}
264+
(uint256 x, uint256 y) = vm.publicKeyP256(privateKey);
265+
266+
// Note that `vm.signP256` can produce `s` above `N / 2`.
267+
(bytes32 r, bytes32 s) = vm.signP256(privateKey, hash);
268+
269+
if (uint256(s) > P256.N / 2) {
270+
assertFalse(P256.verifySignature(hash, r, s, bytes32(x), bytes32(y)));
271+
assertTrue(P256.verifySignature(hash, r, P256.normalized(s), bytes32(x), bytes32(y)));
272+
} else {
273+
assertTrue(P256.verifySignature(hash, r, s, bytes32(x), bytes32(y)));
274+
}
275+
assertTrue(P256.verifySignatureAllowMalleability(hash, r, s, bytes32(x), bytes32(y)));
276+
}
251277
}
252278

253279
/// @dev Library to derive P256 public key from private key

test/utils/forge-std/Vm.sol

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1651,6 +1651,12 @@ interface VmSafe {
16511651
string calldata language
16521652
) external pure returns (uint256 privateKey);
16531653

1654+
/// Derives secp256r1 public key from the provided `privateKey`.
1655+
function publicKeyP256(uint256 privateKey)
1656+
external
1657+
pure
1658+
returns (uint256 publicKeyX, uint256 publicKeyY);
1659+
16541660
/// Gets the label for the specified address.
16551661
function getLabel(address account) external view returns (string memory currentLabel);
16561662

0 commit comments

Comments
 (0)