Skip to content

Commit

Permalink
P256VERIFY precompile contract (#55)
Browse files Browse the repository at this point in the history
* Add first implementation for P256VERIFY precompile

* Update name

* Fix compilation errors

* skip transforming the `y` coordinate

* Add TODO

* Fix bug in Montgomery REDC

* Update

* Rename functions

* Add temporary playground for testing

* Update submodule commit

* Fix `projectiveDouble`

* Uncomment code

* Use N() instead of P()

* add python script

* Update `projectiveScalarMul`

* Update `binaryExtendedEuclideanAlgorithm`

* add sign function

* fix letra

* Remove unnecessary mod

* wip

* wip

* Make python script pass

* Separate between P and N versions in the precompile

* Fix BEE for P

* Update fallback for debug

* Update python script

* Verification finally working

* Add newline to end of file

* Modularize & refactor `montgomeryModularInverse`

* Remove unused function

* Add missing tab

* Refactor montgomery functions

* Remove unused constants

* Rename montgomery constants

* Modularize REDC

* Remove extra tab

* Remove extra tab

* Modularize intoMontgomeryForm

* Modularize montgomeryModularInverse

* Remove unused function

* Modularize outOfMontgomeryForm

* Modularize montgomeryMul

* Modularize montgomeryAdd

* Modularize montgomerySub

* Remove unnecessary mod to r

* Cleanup & refactor

* Remove playground crate

* Rename & refactor p256 python script

* Add missing documentation

* Remove unnecessary line

* add tests for p256verify

* add tests for p256verify

* Revert "add tests for p256verify"

This reverts commit 821b94f.

* cargo fmt

* Fix tests

* Fix clippy

* Update documentation

* Add EOL

---------

Co-authored-by: Joaquin Carletti <[email protected]>
Co-authored-by: Javier Chatruc <[email protected]>
  • Loading branch information
3 people authored Oct 5, 2023
1 parent 228e0e3 commit 2da5282
Show file tree
Hide file tree
Showing 6 changed files with 888 additions and 12 deletions.
606 changes: 606 additions & 0 deletions precompiles/P256Verify.yul

Large diffs are not rendered by default.

37 changes: 26 additions & 11 deletions scripts/montgomery.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import math

# 2^256
# # 2^256
R = 115792089237316195423570985008687907853269984665640564039457584007913129639936
R_PRIME = 20988524275117001072002809824448087578619730785600314334253784976379291040311
# R^2 = (2^256)^2 = 2^512
R2 = 13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084096
# R^3 = (2^256)^3 = 2^768
R3 = 1552518092300708935148979488462502555256886017116696611139052038026050952686376886330878408828646477950487730697131073206171580044114814391444287275041181139204454976020849905550265285631598444825262999193716468750892846853816057856
# R3 % N
R3_MOD_N = 14921786541159648185948152738563080959093619838510245177710943249661917737183
# R_PRIME = 20988524275117001072002809824448087578619730785600314334253784976379291040311
# # R^2 = (2^256)^2 = 2^512
# R2 = 13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084096
# # R^3 = (2^256)^3 = 2^768
# R3 = 1552518092300708935148979488462502555256886017116696611139052038026050952686376886330878408828646477950487730697131073206171580044114814391444287275041181139204454976020849905550265285631598444825262999193716468750892846853816057856
# # R3 % N
R3_MOD_N = 0x129320ffed6cdeffffffffffffffffffed6cdf000000000000000000129321
# Fp
N = 21888242871839275222246405745257275088696311157297823662689037894645226208583
N = int(0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551)
# R2 % N
R2_MOD_N = 3096616502983703923843567936837374451735540968419076528771170197431451843209
R2_MOD_N = 46533765739406314298121036767150998762426774378559716911348521029833835802274
# N' -> NN' ≡ −1 mod R
N_PRIME = 111032442853175714102588374283752698368366046808579839647964533820976443843465
N_PRIME = 43790243024438006127650828685417305984841428635278707415088219106730833919055

ONE = 6350874878119819312338956282401532409788428879151445726012394534686998597021
TWO = 12701749756239638624677912564803064819576857758302891452024789069373997194042
Expand Down Expand Up @@ -129,3 +129,18 @@ def add(augend, addend):

def sub(minuend, subtrahend):
return add(minuend, N - subtrahend)

x = 0x18905f76a53755c679fb732b7762251075ba95fc5fedb60179e730d418a9143c
y = 0x8571ff1825885d85d2e88688dd21f3258b4ab8e4ba19e45cddf25357ce95560a
a = into(115792089210356248762697446949407573530086143415290314195533631308867097853948)
b = into(41058363725152142129326129780047268409114441015993725554835256314039467401291)
# assert(mul(y, y) == add(mul(x, mul(x, x)), add(mul(a, x), b)))
# print(y*y)
# print(hex(y))
# print(hex(mul(y, y)))
# print(hex(mul(x, mul(x, x))))
# print(hex(mul(a, x)))
# print(hex(add(mul(a, x), b)))
# print(hex(add(mul(x, mul(x, x)), add(mul(a, x), b))))
# print(mul(y, y) == add(mul(x, mul(x, x)), add(mul(a, x), b)))
print(into(1))
128 changes: 128 additions & 0 deletions scripts/secp256r1_ecdsa.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
A = 0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc
B = 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b
N = 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551
P = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff

def is_infinity(x, y):
if x == 0 and y == 0:
return True

def inv_mod(a, mod):
return pow(a, mod - 2, mod)

def add_mod(a,b,mod):
return (a + b) % mod

def sub_mod(a,b,mod):
return add_mod(a, mod - b, mod)

def div_mod(a,b,mod):
return mul_mod(a, inv_mod(b, mod), mod)

def mul_mod(a,b,mod):
return (a * b) % mod

def point_add(x1, y1, x2, y2):
mod = P
if is_infinity(x1, y1) and is_infinity(x2, y2):
return (0, 0)
if is_infinity(x1, y1) and not is_infinity(x2, y2):
return (x2, y2)
if not is_infinity(x1, y1) and is_infinity(x2, y2):
return (x1, y1)
if x1 == x2 and sub_mod(0, y1, mod) == y2:
return (0, 0)
if x1 == x2 and y1 == y2:
return point_double(x1, y1, mod)

m = div_mod(sub_mod(y1, y2, mod), sub_mod(x1, x2, mod), mod)
ret_x = sub_mod(mul_mod(m, m, mod), add_mod(x1, x2, mod), mod)
ret_y = sub_mod(mul_mod(m, sub_mod(x1, ret_x, mod), mod), y1, mod)
return (ret_x, ret_y)

def point_double(x, y):
mod = P
if is_infinity(x, y):
return 0, 0
if y == 0:
return 0, 0
m = div_mod(add_mod(A, mul_mod(3, mul_mod(x, x, mod), mod), mod), add_mod(y, y, mod), mod)
ret_x = sub_mod(mul_mod(m, m, mod), add_mod(x, x, mod), mod)
ret_y = sub_mod(mul_mod(m, sub_mod(x, ret_x, mod), mod), y, mod)
return (ret_x, ret_y)

def is_even(x):
return x % 2 == 0

def scalar_mul(p, n):
multiplier = n
res = (0, 0)

while multiplier > 0:
if not is_even(multiplier):
res = point_add(res[0], res[1], p[0], p[1])
p = point_double(p[0], p[1])

multiplier = multiplier >> 1
return res

'''
ECDSA
'''

def public_key(da, gx, gy):
return scalar_mul((gx, gy), da)

def sign(z, da, k, gx, gy, n):
x, y = scalar_mul((gx, gy), k)
r = x % n
assert(r != 0)

k_inv = pow(k, n-2, n)
assert k_inv * k % n == 1

s = (k_inv * (z + r * da)) % n
assert(s != 0)

return r, s

def verify(z, r, s, public_key_x, public_key_y, gx, gy, n):
s_inv = pow(s, n-2, n)
assert s_inv * s % n == 1

u1 = (z * s_inv) % n
u2 = (r * s_inv) % n

x1, _ = point_add(*scalar_mul((gx, gy), u1),*scalar_mul((public_key_x, public_key_y), u2))
x1 = x1 % n
r = r % n

return x1 == r

def main():
n = N

z = 0x1899fa5c2e77910f63db2d279ae19dea9ec0d2f3b0c8c532c572fe27cd1bedba
da = 245123
k = 901879137

gx = 0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296
gy = 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5

# Signature
r, s = sign(z, da, k, gx, gy, n)

print(hex(r))
print(hex(s))

# Public Key
public_key_x, public_key_y = public_key(da, gx, gy)

print(hex(public_key_x))
print(hex(public_key_y))

# Verification
print(verify(z, r, s, public_key_x, public_key_y, gx, gy, n))

if __name__ == "__main__":
main()
2 changes: 1 addition & 1 deletion submodules/era-test-node
126 changes: 126 additions & 0 deletions tests/tests/p256verify_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
use zksync_web3_rs::types::{Address, Bytes, H160};

mod test_utils;
use test_utils::era_call;

pub const P256VERIFTY_PRECOMPILE_ADDRESS: Address = H160([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x19,
]);

const RESPONSE_VALID: [u8; 32] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
];
const RESPONSE_INVALID: [u8; 32] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
const RESPONSE_ERROR: [u8; 1] = [0];

// Puts the given data into the P256VERIFTY precompile
#[tokio::test]
async fn p256verify_valid_signature_one() {
let era_response = era_call(
P256VERIFTY_PRECOMPILE_ADDRESS,
None,
Some(Bytes::from(hex::decode("93973e2948748003bc6c947d56a47411ea1c812b358be9d0189e2bd0a0b9d11eb03ae0c6a0e3e3ff4af4d16ee034277d34c6a8aa63c502d99b1d162961d07d59114fc42e88471db9de64d0ce23e37800a3b07af311d55119adcc82594b7492bb3caf1e7f618f833b6364862c701c6a1ce93fbeef210ef53f97619a8e0ad5c7b1b6a99bc96565cfdfa61439c441260232c6430726192fbb1cedc36f41570659f2").unwrap())),
)
.await
.unwrap();
assert_eq!(era_response, Bytes::from(RESPONSE_VALID))
}

#[tokio::test]
async fn p256verify_valid_signature_two() {
let era_response = era_call(
P256VERIFTY_PRECOMPILE_ADDRESS,
None,
Some(Bytes::from(hex::decode("5ad83880e16658d7521d4e878521defaf6b43dec1dbd69e514c09ab8f1f2ffe255affc6e5faba2ece4d686fd0ca1ed497325bcc2557b4186a54c62d244e692b5871c518be8c56e7f5c901933fdab317efafc588b3e04d19d9a27b29aad8d9e690dca12ea554ca09172dcba021d5965cdf3510180776207c73ade33b75e964bfeb48e217c2059c99a9a36a0297caaaff294b4dc080c5fc78f6af3bab3643c70c4").unwrap())),
)
.await
.unwrap();
assert_eq!(era_response, Bytes::from(RESPONSE_VALID))
}

#[tokio::test]
async fn p256verify_invalid_signature() {
let era_response = era_call(
P256VERIFTY_PRECOMPILE_ADDRESS,
None,
Some(Bytes::from(hex::decode("4ad83880e16658d7521d4e878521defaf6b43dec1dbd69e514c09ab8f1f2ffe255affc6e5faba2ece4d686fd0ca1ed497325bcc2557b4186a54c62d244e692b5871c518be8c56e7f5c901933fdab317efafc588b3e04d19d9a27b29aad8d9e690dca12ea554ca09172dcba021d5965cdf3510180776207c73ade33b75e964bfeb48e217c2059c99a9a36a0297caaaff294b4dc080c5fc78f6af3bab3643c70c4").unwrap())),
)
.await
.unwrap();
assert_eq!(era_response, Bytes::from(RESPONSE_INVALID))
}

#[tokio::test]
async fn p256verify_invalid_r() {
let era_response = era_call(
P256VERIFTY_PRECOMPILE_ADDRESS,
None,
Some(Bytes::from(hex::decode("5ad83880e16658d7521d4e878521defaf6b43dec1dbd69e514c09ab8f1f2ffe2FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632552a88a96ec0a98f29280ddffa35d63fb815c1d1d9c674838f01c4e49371e382983131c7301e8ac9e75cc8008b27e136e452a4e5b6112eae1296be30a0fa7274d5b9f5dde779183b71d1e50ac1cbcdbc52b62807ceb829000ab2986761e92f852e3").unwrap())),
)
.await
.unwrap();
assert_eq!(era_response, Bytes::from(RESPONSE_ERROR))
}

#[tokio::test]
async fn p256verify_invalid_s() {
let era_response = era_call(
P256VERIFTY_PRECOMPILE_ADDRESS,
None,
Some(Bytes::from(hex::decode("5ad83880e16658d7521d4e878521defaf6b43dec1dbd69e514c09ab8f1f2ffe255affc6e5faba2ece4d686fd0ca1ed497325bcc2557b4186a54c62d244e692b5FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC6325520dca12ea554ca09172dcba021d5965cdf3510180776207c73ade33b75e964bfeb48e217c2059c99a9a36a0297caaaff294b4dc080c5fc78f6af3bab3643c70c4").unwrap())),
)
.await
.unwrap();
assert_eq!(era_response, Bytes::from(RESPONSE_ERROR))
}

#[tokio::test]
async fn p256verify_public_key_inf() {
let era_response = era_call(
P256VERIFTY_PRECOMPILE_ADDRESS,
None,
Some(Bytes::from(hex::decode("93973e2948748003bc6c947d56a47411ea1c812b358be9d0189e2bd0a0b9d11eb03ae0c6a0e3e3ff4af4d16ee034277d34c6a8aa63c502d99b1d162961d07d59114fc42e88471db9de64d0ce23e37800a3b07af311d55119adcc82594b7492bb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap())),
)
.await
.unwrap();
assert_eq!(era_response, Bytes::from(RESPONSE_ERROR))
}

#[tokio::test]
async fn p256verify_public_key_x_not_in_field() {
let era_response = era_call(
P256VERIFTY_PRECOMPILE_ADDRESS,
None,
Some(Bytes::from(hex::decode("93973e2948748003bc6c947d56a47411ea1c812b358be9d0189e2bd0a0b9d11eb03ae0c6a0e3e3ff4af4d16ee034277d34c6a8aa63c502d99b1d162961d07d59114fc42e88471db9de64d0ce23e37800a3b07af311d55119adcc82594b7492bbffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb6a99bc96565cfdfa61439c441260232c6430726192fbb1cedc36f41570659f2").unwrap())),
)
.await
.unwrap();
assert_eq!(era_response, Bytes::from(RESPONSE_ERROR))
}

#[tokio::test]
async fn p256verify_public_key_y_not_in_field() {
let era_response = era_call(
P256VERIFTY_PRECOMPILE_ADDRESS,
None,
Some(Bytes::from(hex::decode("93973e2948748003bc6c947d56a47411ea1c812b358be9d0189e2bd0a0b9d11eb03ae0c6a0e3e3ff4af4d16ee034277d34c6a8aa63c502d99b1d162961d07d59114fc42e88471db9de64d0ce23e37800a3b07af311d55119adcc82594b7492bb3caf1e7f618f833b6364862c701c6a1ce93fbeef210ef53f97619a8e0ad5c7b1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap())),
)
.await
.unwrap();
assert_eq!(era_response, Bytes::from(RESPONSE_ERROR))
}

#[tokio::test]
async fn p256verify_public_key_not_in_curve() {
let era_response = era_call(
P256VERIFTY_PRECOMPILE_ADDRESS,
None,
Some(Bytes::from(hex::decode("5ad83880e16658d7521d4e878521defaf6b43dec1dbd69e514c09ab8f1f2ffe255affc6e5faba2ece4d686fd0ca1ed497325bcc2557b4186a54c62d244e692b5871c518be8c56e7f5c901933fdab317efafc588b3e04d19d9a27b29aad8d9e690dca12ea554ca09172dcba021d5965cdf3510180776207c73ade33b75e964bffb48e217c2059c99a9a36a0297caaaff294b4dc080c5fc78f6af3bab3643c70c5").unwrap())),
)
.await
.unwrap();
assert_eq!(era_response, Bytes::from(RESPONSE_ERROR))
}
1 change: 1 addition & 0 deletions tests/tests/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub fn era_provider() -> Provider<Http> {
Provider::try_from(url).unwrap()
}

#[allow(dead_code)]
pub async fn eth_call(
precompile_address: Address,
inputs: Option<&[&str]>,
Expand Down

0 comments on commit 2da5282

Please sign in to comment.