diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 20c5d1c96b..46dd813d03 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -106,6 +106,7 @@ var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{ var PrecompiledContractsMina = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{0x50}): &MinaPoseidon{}, + common.BytesToAddress([]byte{0x51}): &MinaSigner{}, } var ( diff --git a/core/vm/mina_contracts.go b/core/vm/mina_contracts.go index 7acdf6b4bf..80225be77e 100644 --- a/core/vm/mina_contracts.go +++ b/core/vm/mina_contracts.go @@ -7,11 +7,16 @@ package vm import "C" import ( "bytes" - "math/big" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/crypto" ) +var sol_bool, _ = abi.NewType("bool", "", nil) +var sol_uint8, _ = abi.NewType("uint8", "", nil) +var sol_bytes32, _ = abi.NewType("bytes32", "", nil) +var sol_bytes32Arr, _ = abi.NewType("bytes32[]", "", nil) + type MinaPoseidon struct{} func (c *MinaPoseidon) RequiredGas(input []byte) uint64 { @@ -19,32 +24,37 @@ func (c *MinaPoseidon) RequiredGas(input []byte) uint64 { } // 0x1f831f84 -var minaPoseidonSignature = crypto.Keccak256([]byte("poseidonHash(uint8,bytes32[])"))[:4] - -const networkIdIndex = 0 -const fieldsHeadIndex = networkIdIndex + 32 +var poseidonHashSignature = crypto.Keccak256([]byte("poseidonHash(uint8,bytes32[])"))[:4] func (c *MinaPoseidon) Run(input []byte) ([]byte, error) { - if !bytes.Equal(input[:4], minaPoseidonSignature) { + if len(input) < 4+64 || !bytes.Equal(input[:4], poseidonHashSignature) { return nil, ErrExecutionReverted } calldata := input[4:] - networkId := new(big.Int).SetBytes(getData(calldata, networkIdIndex, 32)).Uint64() + unpacked, err := (abi.Arguments{{ + Type: sol_uint8}, // networkId + {Type: sol_bytes32Arr}, // fields + }).Unpack(calldata) - lenIndex := new(big.Int).SetBytes(getData(calldata, fieldsHeadIndex, 32)).Uint64() - fieldsLen := new(big.Int).SetBytes(getData(calldata, lenIndex, 32)).Uint64() + if err != nil { + return nil, err + } - dataIndex := lenIndex + 32 - fields := calldata[dataIndex : dataIndex+fieldsLen*32] + networkId := unpacked[0].(uint8) + fields := unpacked[1].([][32]uint8) + + if len(fields) == 0 { + return nil, ErrExecutionReverted + } output_buffer := [32]byte{} if !C.poseidon_hash( - uint32(networkId), - (*C.uint8_t)(&fields[0]), - C.uintptr_t(fieldsLen), + C.uint8_t(networkId), + (*C.uint8_t)(&fields[0][0]), + C.uintptr_t(len(fields)), (*C.uint8_t)(&output_buffer[0]), ) { return nil, ErrExecutionReverted @@ -52,3 +62,61 @@ func (c *MinaPoseidon) Run(input []byte) ([]byte, error) { return output_buffer[:], nil } + +type MinaSigner struct{} + +func (c *MinaSigner) RequiredGas(input []byte) uint64 { + return 1000 +} + +// 0x462e39d6 +var verifySignature = crypto.Keccak256([]byte("verify(uint8,bytes32,bytes32,bytes32,bytes32,bytes32[])"))[:4] + +func (c *MinaSigner) Run(input []byte) ([]byte, error) { + if len(input) < 4+64 || !bytes.Equal(input[:4], verifySignature) { + return nil, ErrExecutionReverted + } + + calldata := input[4:] + + unpacked, err := (abi.Arguments{ + {Type: sol_uint8}, // networkId + {Type: sol_bytes32}, // pubKeyX + {Type: sol_bytes32}, // pubKeyY + {Type: sol_bytes32}, // signatureRX + {Type: sol_bytes32}, // signatureS + {Type: sol_bytes32Arr}, // fields + }).Unpack(calldata) + + if err != nil { + return nil, err + } + + networkId := unpacked[0].(uint8) + pubKeyX := unpacked[1].([32]uint8) + pubKeyY := unpacked[2].([32]uint8) + signatureRX := unpacked[3].([32]uint8) + signatureS := unpacked[4].([32]uint8) + fields := unpacked[5].([][32]uint8) + + if len(fields) == 0 { + return nil, ErrExecutionReverted + } + + output_buffer := false + + if !C.verify( + C.uint8_t(networkId), + (*C.uint8_t)(&pubKeyX[0]), + (*C.uint8_t)(&pubKeyY[0]), + (*C.uint8_t)(&signatureRX[0]), + (*C.uint8_t)(&signatureS[0]), + (*C.uint8_t)(&fields[0][0]), + C.uintptr_t(len(fields)), + (*C.bool)(&output_buffer), + ) { + return nil, ErrExecutionReverted + } + + return abi.Arguments{{Type: sol_bool}}.Pack(output_buffer) +} diff --git a/mina/src/lib.rs b/mina/src/lib.rs index 3a98a53864..41e38d1f29 100644 --- a/mina/src/lib.rs +++ b/mina/src/lib.rs @@ -3,7 +3,8 @@ mod mina; use std::array::TryFromSliceError; use mina::{Message, NetworkId}; -use mina_signer::{BaseField, CurvePoint, PubKey, Signature, ScalarField}; +use mina_hasher::Hashable; +use mina_signer::{BaseField, CurvePoint, PubKey, ScalarField, Signature}; use o1_utils::FieldHelpers; pub const FIELD_SIZE: usize = 32; @@ -16,9 +17,9 @@ pub extern "C" fn poseidon_hash( output_ptr: *mut u8, // 32 bytes ) -> bool { let network_id = match network_id { - 0x00 => NetworkId::TESTNET, - 0x01 => NetworkId::MAINNET, - 0xff => NetworkId::NULLNET, + 0x00 => NetworkId::MAINNET, + 0x01 => NetworkId::TESTNET, + 0x02 => NetworkId::NULLNET, _ => return false, }; @@ -47,6 +48,66 @@ pub extern "C" fn poseidon_hash( true } +#[no_mangle] +pub extern "C" fn verify( + network_id: u8, + pubkey_x: *const u8, + pubkey_y: *const u8, + sig_rx: *const u8, + sig_s: *const u8, + field_ptr: *const u8, + field_len: usize, + output_ptr: *mut bool, +) -> bool { + let network_id = match network_id { + 0x00 => NetworkId::MAINNET, + 0x01 => NetworkId::TESTNET, + 0x02 => NetworkId::NULLNET, + _ => return false, + }; + + let pubkey_x = unsafe { std::slice::from_raw_parts(pubkey_x, FIELD_SIZE) }; + let pubkey_y = unsafe { std::slice::from_raw_parts(pubkey_y, FIELD_SIZE) }; + + let pubkey = PubKey::from_point_unsafe(CurvePoint::new( + BaseField::from_bytes(pubkey_x).unwrap(), + BaseField::from_bytes(pubkey_y).unwrap(), + false, + )); + + let sig_rx = unsafe { std::slice::from_raw_parts(sig_rx, FIELD_SIZE) }; + let sig_s = unsafe { std::slice::from_raw_parts(sig_s, FIELD_SIZE) }; + + let signature = Signature::new( + BaseField::from_bytes(sig_rx).unwrap(), + ScalarField::from_bytes(sig_s).unwrap(), + ); + + let fields = unsafe { std::slice::from_raw_parts(field_ptr, field_len * FIELD_SIZE) }; + + let fields = match fields + .chunks(FIELD_SIZE) + .map(|chunk| chunk[..32].try_into()) + .collect::, TryFromSliceError>>() + { + Ok(fields) => fields, + Err(_) => return false, + }; + + let msg = match Message::from_bytes_slice(&fields) { + Ok(msg) => msg, + Err(_) => return false, + }; + + println!("{:?}", msg.to_roinput().to_fields()[0].to_biguint()); + + let result = mina::verify(&signature, &pubkey, &msg, network_id); + + unsafe { *output_ptr = result }; + + true +} + #[cfg(test)] mod tests { // use super::*; diff --git a/mina/src/mina.rs b/mina/src/mina.rs index 6f64d687d0..f10ef286b7 100644 --- a/mina/src/mina.rs +++ b/mina/src/mina.rs @@ -1,13 +1,13 @@ use mina_hasher::{DomainParameter, Hashable, Hasher, ROInput}; -use mina_signer::{BaseField, PubKey, Signer, Signature}; +use mina_signer::{BaseField, PubKey, Signature, Signer}; use o1_utils::{field_helpers::FieldHelpersError, FieldHelpers}; #[derive(Debug, Clone)] #[repr(C)] pub enum NetworkId { - TESTNET = 0x00, - MAINNET = 0x01, - NULLNET = 0xff, + MAINNET = 0x00, + TESTNET = 0x01, + NULLNET = 0x02, } impl From for u8 { @@ -24,7 +24,7 @@ impl DomainParameter for NetworkId { #[derive(Clone)] pub struct Message { - fields: Vec, + pub fields: Vec, } impl Message { @@ -61,3 +61,14 @@ pub fn poseidon(msg: &Message, network_id: NetworkId) -> BaseField { hasher.hash(msg) } + +pub fn verify( + signature: &Signature, + pubkey: &PubKey, + msg: &Message, + network_id: NetworkId, +) -> bool { + let mut signer = mina_signer::create_kimchi::(network_id); + + signer.verify(signature, pubkey, msg) +}