Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RIP 7696 : provide asset code #20

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions RIPS/rip-7696.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,36 @@ created: 2024-03-22

This proposal creates two precompiled contracts that perform two point multiplication and sum then over any elliptic curve given `p`, `a`,`b` curve parameters, `Px1`,`Py1` and`Qx2`,`Qy2` coordinates of points P and Q, `u`,`v` two scalars. Thus it computes the value uP+vQ over any given weierstrass curve. One of the precompiles provide extra data (512 bits) to enable a consequent speed-up to any curve. This extra data consists in the points $P_{128}=2^{128}P$ and $Q_{128}=2^{128}Q$.


## Motivation

Account abstraction (EIP 4337, EIP7560) enables to replace EoA with non native signature algorithms. While RIP7212 focuses only on P256, there are many other elliptic curves of interest, subject to change according to latest advances either in ZK proving systems, hardware integration or cross chains requirements. This precompiles can achieve many goals such as Stealth, WebAuthn, Schnorr signatures and cheap bridges with other L2s. While most authentication scheme relies today on ECDSA, Schnorr versions are more MPC and ZK-friendly (faster and more secure). Today one can tweak `ecrecover()` opcode to perform scalar multiplication, given an additional hash. Adding a generic multiplication, in conjugaison with Account Abstraction open the gate for many cheap and powerful use cases. This is a non-exhaustive list of use cases:
Account abstraction (EIP 4337, EIP7560) enables to replace EoA with non native signature algorithms. While RIP7212 focuses only on P256,
there are many other elliptic curves of interest, subject to change according to latest advances either in ZK proving systems, hardware integration or cross chains requirements. This precompiles can achieve many goals such as Stealth, WebAuthn, Schnorr signatures and cheap bridges with other L2s. While most authentication scheme relies today on ECDSA, Schnorr versions are more MPC and ZK-friendly (faster and more secure). Today one can tweak `ecrecover()` opcode to perform scalar multiplication, given an additional hash. Adding a generic multiplication, in conjugaison with Account Abstraction open the gate for many cheap and powerful use cases. This is a non-exhaustive list of use cases:


1. **ed25519 :** Apple secure enclave, Webauthn, OpenSSL, Farcaster, bridges with Cosmos, Solana ecosystems.

2. **secp256r1 :** Most of previous use cases plus Android Keystore, Passkeys.

3. **bn254-G1 :** Zcash, Tornado Cash, as specified by EIP1962.

4. **Jujub :** Circom proving system compatibility.

4. **BabyJujub :** Circom proving system compatibility.

5. **Stark curve :** Starknet Ecosystem.

6. **Other curves :** Pasta, Vela, sec256q1 for inner argument constructions.

This proposal aims to reach maximum security and cryptographic agility for the key management.

This proposal aims to reach maximum security and cryptographic agility for the key management.
While a generic MSM (as proposed by EIP2537, but not limited to BLS12381) would be superior, the variable length and complexity of possible tradeoffs seems to reduce the probability of acceptance. MSM is mainly targeting ZK uses, while for classical non-pairing based cryptography, DSM is the core required operation.

## Specification

### Constants


### New Precompile
=======
| Name | Value |
|-----------------------|--------|
| FORK_BLOCK | TBD |
Expand All @@ -60,6 +67,7 @@ Any elliptic curve can be expressed under a Weierstrass form defined by the equa

The following requirements **MUST** be checked by the precompiled contract to verify signature components are valid:


- P and Q coordinates verify the curve equation,
- P and Q coordinates are within prime field range (i.e belong to [0..p-1]).

Expand All @@ -68,10 +76,12 @@ The following elements are NOT checked by the precompile:
- the provided curve is safe regarding classic criteria (twist security, embedded degree, rho security, etc.)
- the provided points belongs to the right subgroup (for non prime order curves)


As such it is heavily recommended to avoid custom curves without an extended knowledge and examination of the previous criterias.

### Precompiled Contracts Specification


#### ecMulmuladd

The `ecMulmuladd` precompiled contract is proposed with the following input and outputs, which are big-endian values:
Expand Down Expand Up @@ -138,6 +148,7 @@ The Implementation of the `ecMulmuladdB4` precompiled contract is provided as a

The changes are not directly affecting the protocol security. The security is related to the level of investigation the target curve has been through.


## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).
1 change: 0 additions & 1 deletion assets/rip-7696
Submodule rip-7696 deleted from 518cc6
277 changes: 277 additions & 0 deletions assets/rip-7696/src/elliptic/SCL_mulmuladdX_fullgen_b4.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
/********************************************************************************************/
/*
/* ___ _ _ ___ _ _ _ _
/* / __|_ __ ___ ___| |_| |_ / __|_ _ _ _ _ __| |_ ___ | | (_) |__
/* \__ \ ' \/ _ \/ _ \ _| ' \ | (__| '_| || | '_ \ _/ _ \ | |__| | '_ \
/* |___/_|_|_\___/\___/\__|_||_| \___|_| \_, | .__/\__\___/ |____|_|_.__/
/* |__/|_|
/*
/* Copyright (C) 2023 - Renaud Dubois - This file is part of SCL (Smooth CryptoLib) project
/* License: This software is licensed under MIT License
/*
/********************************************************************************************/
/* This file implements elliptic curve over short weierstrass form, with coefficient a=-3, with xyzz coordinates */
/* It is a custom 4 dimensional version of Shamir's trick (tis not a window)*/
/* (gen= any curve, sw=short weierstrass) */
/* b4=Four dimensional multiexponentiation */
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19 <0.9.0;


//Starting from mload(0x40) this is the mapping in allocated memory
//https://medium.com/@ac1d_eth/technical-exploration-of-inline-assembly-in-solidity-b7d2b0b2bda8
//mapping from 0x40 in memory
uint256 constant _Prec_T8=0x800;
uint256 constant _Ap=0x820;
uint256 constant _y2=0x840;
uint256 constant _zzz2=0x860;
uint256 constant _free=0x880;

//mapping from Q in input to function, contains Qx, Qy, Qx', Qy', p, a, gx, gy, gx', gy'
//where P' is P multiplied by 2 pow 128 for shamir's multidimensional trick
//todo: remove all magic numbers
uint constant _Qx=0x00;
uint constant _Qy=0x20;
uint constant _Qx2pow128=0x40;
uint constant _Qy2pow128=0x60;
uint constant _modp=0x80;
uint constant _a=0xa0;
uint constant _gx=0xc0;
uint constant _gy=0xe0;
uint constant _gpow2p128_x=0x100;
uint constant _gpow2p128_y=0x120;

//this function is for use only after validation of the Q input:
//Q shall belongs to the curve, and different from -P, -P128, -(P+P128), ...
//those 16 values are tested by the ValidateKey function
//due to handling of Neutral element, this function will not work for 16 specific weak keys
//those value are excluded from the
function ecGenMulmuladdX_store(
uint256 [10] memory Q,//store Qx, Qy, Q'x, Q'y p, a, gx, gy, gx2pow128, gy2pow128
uint256 scalar_u,
uint256 scalar_v
) view returns (uint256 X) {
uint256 mask=1<<127;
/* I. precomputations phase */

if(scalar_u==0&&scalar_v==0){
return 0;
}
uint256 Y;
uint256 ZZZ;
uint256 ZZ;

// bytes memory Mem = new bytes(16*4*32);
assembly ("memory-safe") {

mstore(0x40, add(mload(0x40), _Prec_T8))
mstore(add(mload(0x40), _Ap), mload(add(Q, 0x80))) //load modulus into AP addresse

//store 4 256 bits values starting from addr+offset
function mstore4(addr, offset, val1, val2, val3, val4){
mstore(add(offset, addr),val1 )
offset:=add(32, offset)
mstore(add(offset, addr),val2 )
offset:=add(32, offset)
mstore(add(offset, addr),val3 )
offset:=add(32, offset)
mstore(add(offset, addr),val4 )
offset:=add(32, offset)
}
/* I. precomputations */
//allocate memory for 15 projective points, first slot is unused
{
let _modulusp:=mload(add(mload(0x40), _Ap))
//normalized addition of two point, must not be neutral input
function ecAddn2(x1, y1, zz1, zzz1, x2, y2, _p) -> _x, _y, _zz, _zzz {
y1 := sub(_p, y1)
y2 := addmod(mulmod(y2, zzz1, _p), y1, _p)
x2 := addmod(mulmod(x2, zz1, _p), sub(_p, x1), _p)
_x := mulmod(x2, x2, _p) //PP = P^2
_y := mulmod(_x, x2, _p) //PPP = P*PP
_zz := mulmod(zz1, _x, _p) ////ZZ3 = ZZ1*PP
_zzz := mulmod(zzz1, _y, _p) ////ZZZ3 = ZZZ1*PPP
zz1 := mulmod(x1, _x, _p) //Q = X1*PP
_x := addmod(addmod(mulmod(y2, y2, _p), sub(_p, _y), _p), mulmod(sub(_p,2), zz1, _p), _p) //R^2-PPP-2*Q

x1:=mulmod(addmod(zz1, sub(_p, _x), _p), y2, _p)//necessary split not to explose stack
_y := addmod(x1, mulmod(y1, _y, _p), _p) //R*(Q-X3)
}

mstore4(mload(0x40), 128, mload(add(Q,_gx)), mload(add(Q,_gy)), 1, 1) //G the base point
mstore4(mload(0x40), 256, mload(add(Q,_gpow2p128_x)), mload(add(Q,_gpow2p128_y)), 1, 1) //G'=2^128.G

X:=mload(add(Q,_gpow2p128_x))
Y:=mload(add(Q,_gpow2p128_y))
X,Y,ZZ,ZZZ:=ecAddn2( X,Y,1,1, mload(add(Q,_gx)),mload(add(Q,_gy)), _modulusp) //G+G'
mstore4(mload(0x40), 384, X,Y,ZZ,ZZZ) //Q, the public key
mstore4(mload(0x40), 512, mload(Q),mload(add(32,Q)),1,1)

X,Y,ZZ,ZZZ:=ecAddn2( mload(Q),mload(add(Q,32)),1,1, mload(add(Q,_gx)),mload(add(Q,_gy)),_modulusp )//G+Q
mstore4(mload(0x40), 640, X,Y,ZZ,ZZZ)


X:=mload(add(Q,_gpow2p128_x))
Y:=mload(add(Q,_gpow2p128_y))
X,Y,ZZ,ZZZ:=ecAddn2(X,Y,1,1,mload(Q),mload(add(Q,32)), _modulusp)//G'+Q
mstore4(mload(0x40), 768, X,Y,ZZ,ZZZ)

X,Y,ZZ,ZZZ:=ecAddn2( X,Y,ZZ,ZZZ, mload(add(Q,_gx)), mload(add(Q,_gy)), _modulusp)//G'+Q+G
mstore4(mload(0x40), 896, X,Y,ZZ,ZZZ)

mstore4(mload(0x40), 1024, mload(add(Q, 64)), mload(add(Q, 96)),1,1) //Q'=2^128.Q


X,Y,ZZ,ZZZ:=ecAddn2(mload(add(Q, 64)), mload(add(Q, 96)),1,1, mload(add(Q,_gx)),mload(add(Q,_gy)), mload(add(mload(0x40), _Ap)) )//Q'+G
mstore4(mload(0x40), 1152, X,Y,ZZ,ZZZ)


X:=mload(add(Q,_gpow2p128_x))
Y:=mload(add(Q,_gpow2p128_y))
X,Y,ZZ,ZZZ:=ecAddn2(mload(add(Q, 64)), mload(add(Q, 96)),1,1, X,Y, mload(add(mload(0x40), _Ap)) )//Q'+G'
mstore4(mload(0x40), 1280, X,Y,ZZ,ZZZ)

X,Y,ZZ,ZZZ:=ecAddn2(X, Y, ZZ, ZZZ, mload(add(Q,_gx)), mload(add(Q,_gy)), mload(add(mload(0x40), _Ap)) )//Q'+G'+G
mstore4(mload(0x40), 1408, X,Y,ZZ,ZZZ)

X,Y,ZZ,ZZZ:=ecAddn2( mload(Q),mload(add(Q,32)),1,1, mload(add(Q, 64)), mload(add(Q, 96)), mload(add(mload(0x40), _Ap)) )//Q+Q'
mstore4(mload(0x40), 1536, X,Y,ZZ,ZZZ)

X,Y,ZZ,ZZZ:=ecAddn2( X,Y,ZZ,ZZZ, mload(add(Q,_gx)), mload(add(Q,_gy)), mload(add(mload(0x40), _Ap)) )//Q+Q'+G
mstore4(mload(0x40), 1664, X,Y,ZZ,ZZZ)

X:= mload(add(768, mload(0x40)) )//G'+Q
Y:= mload(add(800, mload(0x40)) )
ZZ:= mload(add(832, mload(0x40)) )
ZZZ:=mload(add(864, mload(0x40)) )
X,Y,ZZ,ZZZ:=ecAddn2( X,Y,ZZ,ZZZ,mload(add(Q, 64)), mload(add(Q, 96)), mload(add(mload(0x40), _Ap)) )//G'+Q+Q'+
mstore4(mload(0x40), 1792, X,Y,ZZ,ZZZ)

X,Y,ZZ,ZZZ:=ecAddn2( X,Y,ZZ,ZZZ,mload(add(Q,0xc0)),mload(add(Q,_gy)), mload(add(mload(0x40), _Ap)) )//G'+Q+Q'+G
// Prec[15]
mstore4(mload(0x40), 1920, X,Y,ZZ,ZZZ)
}
/*II. First MSB bit*/
ZZZ:=0
for {} iszero(ZZZ) { mask := shr(1, mask) }{
ZZZ:=add(add(sub(1,iszero(and(scalar_u, mask))), shl(1,sub(1,iszero(and(shr(128, scalar_u), mask))))),
add(shl(2,sub(1,iszero(and(scalar_v, mask)))), shl(3,sub(1,iszero(and(shr(128, scalar_v), mask))))))

}

X:=mload(add(mload(0x40),shl(7,ZZZ)))//X
Y:=mload(add(mload(0x40),add(32, shl(7,ZZZ))))//Y
ZZ:=mload(add(mload(0x40),add(64, shl(7,ZZZ))))//ZZ
ZZZ:=mload(add(mload(0x40),add(96, shl(7,ZZZ))))//ZZZ


/*III. Main loop */
//(X,Y,ZZ,ZZZ)=ec_Dbl(X,Y,ZZ,ZZZ);
//TODO, replace mul by shifts
for {} gt(mask, 0) { mask := shr(1, mask) } {
let Mem:=mload(0x40)
let _p:=mload(add(Mem, _Ap))

{
//X,Y,ZZ,ZZZ:=ecDblNeg(X,Y,ZZ,ZZZ), not having it inplace increase by 12K the cost of the function

let T1 := mulmod(2, Y, _p) //U = 2*Y1, y free
let T2 := mulmod(T1, T1, _p) // V=U^2
let T3 := mulmod(X, T2, _p) // S = X1*V
T1 := mulmod(T1, T2, _p) // W=UV
let T4:=mulmod(mload(add(Q,0xa0)),mulmod(ZZ,ZZ,_p),_p)

T4 := addmod(mulmod(3, mulmod(X,X,_p),_p),T4,_p)//M=3*X12+aZZ12
ZZZ := mulmod(T1, ZZZ, _p) //zzz3=W*zzz1
ZZ := mulmod(T2, ZZ, _p) //zz3=V*ZZ1
X:=sub(_p,2)//-2
X := addmod(mulmod(T4, T4, _p), mulmod(X, T3, _p), _p) //X3=M^2-2S
T2 := mulmod(T4, addmod(X, sub(_p, T3), _p), _p) //-M(S-X3)=M(X3-S)
Y := addmod(mulmod(T1, Y, _p), T2, _p) //-Y3= W*Y1-M(S-X3), we replace Y by -Y to avoid a sub in ecAdd
//Y:=sub(p,Y)*/

}

// let T4:=shl(128,mask)
// let T1:=add(add(sub(1,iszero(and(scalar_u, mask))), shl(1,sub(1,iszero(and(scalar_u, T4))))),
// add(shl(2,sub(1,iszero(and(scalar_v, mask)))), shl(3,sub(1,iszero(and(T4, scalar_v))))))

let T1:=add(add(sub(1,iszero(and(scalar_u, mask))), shl(1,sub(1,iszero(and(shr(128, scalar_u), mask))))),
add(shl(2,sub(1,iszero(and(scalar_v, mask)))), shl(3,sub(1,iszero(and(shr(128, scalar_v), mask))))))

if iszero(T1) {
Y := sub(_p, Y)
continue
}
//inlined ec_Add
T1:=shl(7, T1)//Memmputed value address offset

let T4:=mload(add(Mem,T1))//X2
mstore(add(Mem, _zzz2), mload(add(Mem,add(96,T1))))//ZZZ2


mstore(add(Mem,_y2), addmod(mulmod( mload(add(Mem,add(32,T1))), ZZZ, _p), mulmod(Y,mload(add(Mem, _zzz2)), _p), _p))//R=S2-S1, sub avoided
T1:=mload(add(Mem,add(64,T1)))//zz2
let T2 := addmod(mulmod(T4, ZZ, _p), sub(_p, mulmod(X,T1,_p)), _p)//P=U2-U1

//special case ecAdd(P,P)=EcDbl
if iszero(mload(add(Mem,_y2))) {
if iszero(T2) {
T1 := mulmod(sub(_p,2), Y, _p) //U = 2*Y1, y free
T2 := mulmod(T1, T1, _p) // V=U^2
mstore(add(Mem,_y2), mulmod(X, T2, _p)) // S = X1*V

T1 := mulmod(T1, T2, _p) // W=UV
T4:=mulmod(mload(add(Q,0xa0)),mulmod(ZZ,ZZ,_p),_p)
T4 := addmod(mulmod(3, mulmod(X,X,_p),_p),T4,_p)//M=3*X12+aZZ12 //M

ZZZ := mulmod(T1, ZZZ, _p) //zzz3=W*zzz1
ZZ := mulmod(T2, ZZ, _p) //zz3=V*ZZ1, V free

X := addmod(mulmod(T4, T4, _p), mulmod(sub(_p,2), mload(add(Mem, _y2)), _p), _p) //X3=M^2-2S
T2 := mulmod(T4, addmod(mload(add(Mem, _y2)), sub(_p, X), _p), _p) //M(S-X3)

Y := addmod(T2, mulmod(T1, Y, _p), _p) //Y3= M(S-X3)-W*Y1

continue
}
}
T4 := mulmod(T2, T2, _p) //PP
T2 := mulmod(T4, T2, _p) //PPP
ZZ := mulmod(mulmod(ZZ, T4,_p), T1 ,_p)//zz3=zz1*zz2*PP
T1:= mulmod(X,T1, _p)
ZZZ := mulmod(mulmod(ZZZ, T2, _p), mload(add(Mem, _zzz2)),_p) // zzz3=zzz1*zzz2*PPP
X := addmod(addmod(mulmod(mload(add(Mem, _y2)), mload(add(Mem, _y2)), _p), sub(_p, T2), _p), mulmod( T1 ,mulmod(sub(_p,2), T4, _p),_p ), _p)// R2-PPP-2*U1*PP
T4 := mulmod(T1, T4, _p)///Q=U1*PP
Y := addmod(mulmod(addmod(T4, sub(_p, X), _p), mload(add(Mem, _y2)), _p), mulmod(mulmod(Y,mload(add(Mem, _zzz2)), _p), T2, _p), _p)// R*(Q-X3)-S1*PPP

}//endloop
/* IV. Normalization */
//(X,)=ec_Normalize(X,Y,ZZ,ZZZ);
let _p:=mload(add(mload(0x40), _Ap))
mstore(0x40, _free)
let T := mload(0x40)
mstore(add(T, 0x60), ZZ)
//(X,Y)=ecZZ_SetAff(X,Y,zz, zzz);
//T[0] = inverseModp_Hard(T[0], p); //1/zzz, inline modular inversion using Memmpile:
// Define length of base, exponent and modulus. 0x20 == 32 bytes
mstore(T, 0x20)
mstore(add(T, 0x20), 0x20)
mstore(add(T, 0x40), 0x20)
// Define variables base, exponent and modulus
//mstore(add(pointer, 0x60), u)
mstore(add(T, 0x80), sub(_p,2))
mstore(add(T, 0xa0), _p)

// Call the precompiled contract 0x05 = ModExp
if iszero(staticcall(not(0), 0x05, T, 0xc0, T, 0x20)) { revert(0, 0) }

//Y:=mulmod(Y,zzz,p)//Y/zzz
//zz :=mulmod(zz, mload(T),p) //1/z
//zz:= mulmod(zz,zz,p) //1/zz
X := mulmod(X, mload(T), _p) //X/zz

}//end assembly
}


Loading