mirror of
https://github.com/serai-dex/serai.git
synced 2024-11-16 17:07:35 +00:00
ethereum: implement schnorr verification contract deployment and related crypto (#36)
* basic schnorr verify working * add schnorr-verify as submodule * remove previous code * Misc Ethereum work which will probably be disregarded * add ecrecover hack test, worksgit add src/ * merge w develop * starting w/ rust-web3 * trying to use ethers * deploy_schnorr_verifier_contract finally working * modify EthereumHram to use 27/28 for point parity * updated address calc, solidity schnorr verify now working * add verify failure to test * update readme * move ethereum/ to coins/ * un fmt coins/monero * update .gitmodules * fix cargo paths * fix coins/monero * add #[allow(non_snake_case)] * un-fmt stuff * move crypto to coins/ethereum * move unit tests to ethereum/tests * remove js, build w ethers * update .gitignore * address comments * add q != 0 check * update contract param order * update contract license to AGPL * update ethereum-serai license to GPL and fmt * GPLv3 for ethereum-serai * AGPLv3 for ethereum-serai * actually fix license Co-authored-by: Luke Parker <lukeparker5132@gmail.com>
This commit is contained in:
parent
e67033a207
commit
c589743e2b
13 changed files with 408 additions and 2 deletions
|
@ -8,6 +8,7 @@ members = [
|
||||||
"crypto/dleq",
|
"crypto/dleq",
|
||||||
"crypto/frost",
|
"crypto/frost",
|
||||||
|
|
||||||
|
"coins/ethereum",
|
||||||
"coins/monero",
|
"coins/monero",
|
||||||
|
|
||||||
"processor",
|
"processor",
|
||||||
|
|
3
coins/ethereum/.gitignore
vendored
Normal file
3
coins/ethereum/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# solidity build outputs
|
||||||
|
cache
|
||||||
|
artifacts
|
28
coins/ethereum/Cargo.toml
Normal file
28
coins/ethereum/Cargo.toml
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
[package]
|
||||||
|
name = "ethereum-serai"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "An Ethereum library supporting Schnorr signing and on-chain verification"
|
||||||
|
license = "AGPL-3.0-only"
|
||||||
|
authors = ["Luke Parker <lukeparker5132@gmail.com>", "Elizabeth Binks <elizabethjbinks@gmail.com>"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
thiserror = "1"
|
||||||
|
serde_json = "1.0"
|
||||||
|
serde = "1.0"
|
||||||
|
hex-literal = "0.3"
|
||||||
|
|
||||||
|
ethers = { git = "https://github.com/gakonst/ethers-rs", features = ["abigen", "ethers-solc"] }
|
||||||
|
eyre = "0.6"
|
||||||
|
|
||||||
|
k256 = { version = "0.11", features = ["arithmetic", "keccak256", "ecdsa"] }
|
||||||
|
frost = { package = "modular-frost", path = "../../crypto/frost", features = ["secp256k1"] }
|
||||||
|
sha3 = "0.10"
|
||||||
|
group = "0.12"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
rand = "0.8"
|
||||||
|
tokio = { version = "1.19", features = ["macros"] }
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
ethers-solc = { git = "https://github.com/gakonst/ethers-rs" }
|
21
coins/ethereum/README.md
Normal file
21
coins/ethereum/README.md
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# Ethereum
|
||||||
|
|
||||||
|
This package contains Ethereum-related functionality, specifically deploying and interacting with Serai contracts.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- anvil & solc & geth's abigen (see [here](https://github.com/gakonst/ethers-rs#running-the-tests))
|
||||||
|
|
||||||
|
## To test
|
||||||
|
|
||||||
|
To compile contracts:
|
||||||
|
```
|
||||||
|
cargo build
|
||||||
|
```
|
||||||
|
|
||||||
|
This places the compiled artifact into `artifacts/`.
|
||||||
|
|
||||||
|
To run Rust tests (you must have compiled the contracts first):
|
||||||
|
```
|
||||||
|
cargo test
|
||||||
|
```
|
15
coins/ethereum/build.rs
Normal file
15
coins/ethereum/build.rs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
use ethers_solc::{Project, ProjectPathsConfig};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println!("cargo:rerun-if-changed=contracts/Schnorr.sol");
|
||||||
|
|
||||||
|
// configure the project with all its paths, solc, cache etc.
|
||||||
|
let project = Project::builder()
|
||||||
|
.paths(ProjectPathsConfig::hardhat(env!("CARGO_MANIFEST_DIR")).unwrap())
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
project.compile().unwrap();
|
||||||
|
|
||||||
|
// Tell Cargo that if a source file changes, to rerun this build script.
|
||||||
|
project.rerun_if_sources_changed();
|
||||||
|
}
|
36
coins/ethereum/contracts/Schnorr.sol
Normal file
36
coins/ethereum/contracts/Schnorr.sol
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
//SPDX-License-Identifier: AGPLv3
|
||||||
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
|
// see https://github.com/noot/schnorr-verify for implementation details
|
||||||
|
contract Schnorr {
|
||||||
|
// secp256k1 group order
|
||||||
|
uint256 constant public Q =
|
||||||
|
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141;
|
||||||
|
|
||||||
|
// parity := public key y-coord parity (27 or 28)
|
||||||
|
// px := public key x-coord
|
||||||
|
// message := 32-byte message
|
||||||
|
// s := schnorr signature
|
||||||
|
// e := schnorr signature challenge
|
||||||
|
function verify(
|
||||||
|
uint8 parity,
|
||||||
|
bytes32 px,
|
||||||
|
bytes32 message,
|
||||||
|
bytes32 s,
|
||||||
|
bytes32 e
|
||||||
|
) public view returns (bool) {
|
||||||
|
// ecrecover = (m, v, r, s);
|
||||||
|
bytes32 sp = bytes32(Q - mulmod(uint256(s), uint256(px), Q));
|
||||||
|
bytes32 ep = bytes32(Q - mulmod(uint256(e), uint256(px), Q));
|
||||||
|
|
||||||
|
require(sp != 0);
|
||||||
|
// the ecrecover precompile implementation checks that the `r` and `s`
|
||||||
|
// inputs are non-zero (in this case, `px` and `ep`), thus we don't need to
|
||||||
|
// check if they're zero.will make me
|
||||||
|
address R = ecrecover(sp, parity, px, ep);
|
||||||
|
require(R != address(0), "ecrecover failed");
|
||||||
|
return e == keccak256(
|
||||||
|
abi.encodePacked(R, uint8(parity), px, block.chainid, message)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
52
coins/ethereum/src/contract.rs
Normal file
52
coins/ethereum/src/contract.rs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
use crate::crypto::ProcessedSignature;
|
||||||
|
use ethers::{contract::ContractFactory, prelude::*, solc::artifacts::contract::ContractBytecode};
|
||||||
|
use eyre::{eyre, Result};
|
||||||
|
use std::fs::File;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum EthereumError {
|
||||||
|
#[error("failed to verify Schnorr signature")]
|
||||||
|
VerificationError,
|
||||||
|
}
|
||||||
|
|
||||||
|
abigen!(
|
||||||
|
Schnorr,
|
||||||
|
"./artifacts/Schnorr.sol/Schnorr.json",
|
||||||
|
event_derives(serde::Deserialize, serde::Serialize),
|
||||||
|
);
|
||||||
|
|
||||||
|
pub async fn deploy_schnorr_verifier_contract(
|
||||||
|
client: Arc<SignerMiddleware<Provider<Http>, LocalWallet>>,
|
||||||
|
) -> Result<schnorr_mod::Schnorr<SignerMiddleware<Provider<Http>, LocalWallet>>> {
|
||||||
|
let path = "./artifacts/Schnorr.sol/Schnorr.json";
|
||||||
|
let artifact: ContractBytecode = serde_json::from_reader(File::open(path).unwrap()).unwrap();
|
||||||
|
let abi = artifact.abi.unwrap();
|
||||||
|
let bin = artifact.bytecode.unwrap().object;
|
||||||
|
let factory = ContractFactory::new(abi, bin.into_bytes().unwrap(), client.clone());
|
||||||
|
let contract = factory.deploy(())?.send().await?;
|
||||||
|
let contract = Schnorr::new(contract.address(), client);
|
||||||
|
Ok(contract)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn call_verify(
|
||||||
|
contract: &schnorr_mod::Schnorr<SignerMiddleware<Provider<Http>, LocalWallet>>,
|
||||||
|
params: &ProcessedSignature,
|
||||||
|
) -> Result<()> {
|
||||||
|
let ok = contract
|
||||||
|
.verify(
|
||||||
|
params.parity + 27,
|
||||||
|
params.px.to_bytes().into(),
|
||||||
|
params.message.into(),
|
||||||
|
params.s.to_bytes().into(),
|
||||||
|
params.e.to_bytes().into(),
|
||||||
|
)
|
||||||
|
.call()
|
||||||
|
.await?;
|
||||||
|
if ok {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(eyre!(EthereumError::VerificationError))
|
||||||
|
}
|
||||||
|
}
|
104
coins/ethereum/src/crypto.rs
Normal file
104
coins/ethereum/src/crypto.rs
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
use sha3::{Digest, Keccak256};
|
||||||
|
|
||||||
|
use group::Group;
|
||||||
|
use k256::{
|
||||||
|
elliptic_curve::{bigint::ArrayEncoding, ops::Reduce, sec1::ToEncodedPoint, DecompressPoint},
|
||||||
|
AffinePoint, ProjectivePoint, Scalar, U256,
|
||||||
|
};
|
||||||
|
|
||||||
|
use frost::{algorithm::Hram, curve::Secp256k1};
|
||||||
|
|
||||||
|
pub fn keccak256(data: &[u8]) -> [u8; 32] {
|
||||||
|
Keccak256::digest(data).try_into().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hash_to_scalar(data: &[u8]) -> Scalar {
|
||||||
|
Scalar::from_uint_reduced(U256::from_be_slice(&keccak256(data)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn address(point: &ProjectivePoint) -> [u8; 20] {
|
||||||
|
let encoded_point = point.to_encoded_point(false);
|
||||||
|
keccak256(&encoded_point.as_ref()[1 .. 65])[12 .. 32].try_into().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ecrecover(message: Scalar, v: u8, r: Scalar, s: Scalar) -> Option<[u8; 20]> {
|
||||||
|
if r.is_zero().into() || s.is_zero().into() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
let R = AffinePoint::decompress(&r.to_bytes(), v.into());
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
if let Some(R) = Option::<AffinePoint>::from(R) {
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
let R = ProjectivePoint::from(R);
|
||||||
|
|
||||||
|
let r = r.invert().unwrap();
|
||||||
|
let u1 = ProjectivePoint::GENERATOR * (-message * r);
|
||||||
|
let u2 = R * (s * r);
|
||||||
|
let key: ProjectivePoint = u1 + u2;
|
||||||
|
if !bool::from(key.is_identity()) {
|
||||||
|
return Some(address(&key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct EthereumHram {}
|
||||||
|
impl Hram<Secp256k1> for EthereumHram {
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn hram(R: &ProjectivePoint, A: &ProjectivePoint, m: &[u8]) -> Scalar {
|
||||||
|
let a_encoded_point = A.to_encoded_point(true);
|
||||||
|
let mut a_encoded = a_encoded_point.as_ref().to_owned();
|
||||||
|
a_encoded[0] += 25; // Ethereum uses 27/28 for point parity
|
||||||
|
let mut data = address(R).to_vec();
|
||||||
|
data.append(&mut a_encoded);
|
||||||
|
data.append(&mut m.to_vec());
|
||||||
|
Scalar::from_uint_reduced(U256::from_be_slice(&keccak256(&data)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ProcessedSignature {
|
||||||
|
pub s: Scalar,
|
||||||
|
pub px: Scalar,
|
||||||
|
pub parity: u8,
|
||||||
|
pub message: [u8; 32],
|
||||||
|
pub e: Scalar,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn preprocess_signature_for_ecrecover(
|
||||||
|
m: [u8; 32],
|
||||||
|
R: &ProjectivePoint,
|
||||||
|
s: Scalar,
|
||||||
|
A: &ProjectivePoint,
|
||||||
|
chain_id: U256,
|
||||||
|
) -> (Scalar, Scalar) {
|
||||||
|
let processed_sig = process_signature_for_contract(m, R, s, A, chain_id);
|
||||||
|
let sr = processed_sig.s.mul(&processed_sig.px).negate();
|
||||||
|
let er = processed_sig.e.mul(&processed_sig.px).negate();
|
||||||
|
(sr, er)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn process_signature_for_contract(
|
||||||
|
m: [u8; 32],
|
||||||
|
R: &ProjectivePoint,
|
||||||
|
s: Scalar,
|
||||||
|
A: &ProjectivePoint,
|
||||||
|
chain_id: U256,
|
||||||
|
) -> ProcessedSignature {
|
||||||
|
let encoded_pk = A.to_encoded_point(true);
|
||||||
|
let px = &encoded_pk.as_ref()[1 .. 33];
|
||||||
|
let px_scalar = Scalar::from_uint_reduced(U256::from_be_slice(px));
|
||||||
|
let e = EthereumHram::hram(R, A, &[chain_id.to_be_byte_array().as_slice(), &m].concat());
|
||||||
|
ProcessedSignature {
|
||||||
|
s,
|
||||||
|
px: px_scalar,
|
||||||
|
parity: &encoded_pk.as_ref()[0] - 2,
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
message: m,
|
||||||
|
e,
|
||||||
|
}
|
||||||
|
}
|
2
coins/ethereum/src/lib.rs
Normal file
2
coins/ethereum/src/lib.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod contract;
|
||||||
|
pub mod crypto;
|
60
coins/ethereum/tests/contract.rs
Normal file
60
coins/ethereum/tests/contract.rs
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
use ethereum_serai::contract::{call_verify, deploy_schnorr_verifier_contract};
|
||||||
|
use ethers::{prelude::*, utils::Anvil};
|
||||||
|
use std::{convert::TryFrom, sync::Arc, time::Duration};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_deploy_contract() {
|
||||||
|
let anvil = Anvil::new().spawn();
|
||||||
|
let wallet: LocalWallet = anvil.keys()[0].clone().into();
|
||||||
|
let provider =
|
||||||
|
Provider::<Http>::try_from(anvil.endpoint()).unwrap().interval(Duration::from_millis(10u64));
|
||||||
|
let client = Arc::new(SignerMiddleware::new(provider, wallet));
|
||||||
|
|
||||||
|
let _contract = deploy_schnorr_verifier_contract(client).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_ecrecover_hack() {
|
||||||
|
use ethereum_serai::crypto;
|
||||||
|
use ethers::utils::keccak256;
|
||||||
|
use frost::{
|
||||||
|
algorithm::Schnorr,
|
||||||
|
curve::Secp256k1,
|
||||||
|
tests::{algorithm_machines, key_gen, sign},
|
||||||
|
};
|
||||||
|
use k256::elliptic_curve::bigint::ArrayEncoding;
|
||||||
|
use k256::{Scalar, U256};
|
||||||
|
use rand::rngs::OsRng;
|
||||||
|
|
||||||
|
let anvil = Anvil::new().spawn();
|
||||||
|
let wallet: LocalWallet = anvil.keys()[0].clone().into();
|
||||||
|
let provider =
|
||||||
|
Provider::<Http>::try_from(anvil.endpoint()).unwrap().interval(Duration::from_millis(10u64));
|
||||||
|
let chain_id = provider.get_chainid().await.unwrap();
|
||||||
|
let client = Arc::new(SignerMiddleware::new(provider, wallet));
|
||||||
|
|
||||||
|
let keys = key_gen::<_, Secp256k1>(&mut OsRng);
|
||||||
|
let group_key = keys[&1].group_key();
|
||||||
|
|
||||||
|
const MESSAGE: &'static [u8] = b"Hello, World!";
|
||||||
|
let hashed_message = keccak256(MESSAGE);
|
||||||
|
let chain_id = U256::from(Scalar::from(chain_id.as_u32()));
|
||||||
|
|
||||||
|
let full_message = &[chain_id.to_be_byte_array().as_slice(), &hashed_message].concat();
|
||||||
|
|
||||||
|
let sig = sign(
|
||||||
|
&mut OsRng,
|
||||||
|
algorithm_machines(&mut OsRng, Schnorr::<Secp256k1, crypto::EthereumHram>::new(), &keys),
|
||||||
|
full_message,
|
||||||
|
);
|
||||||
|
let mut processed_sig =
|
||||||
|
crypto::process_signature_for_contract(hashed_message, &sig.R, sig.s, &group_key, chain_id);
|
||||||
|
|
||||||
|
let contract = deploy_schnorr_verifier_contract(client).await.unwrap();
|
||||||
|
call_verify(&contract, &processed_sig).await.unwrap();
|
||||||
|
|
||||||
|
// test invalid signature fails
|
||||||
|
processed_sig.message[0] = 0;
|
||||||
|
let res = call_verify(&contract, &processed_sig).await;
|
||||||
|
assert!(res.is_err());
|
||||||
|
}
|
80
coins/ethereum/tests/crypto.rs
Normal file
80
coins/ethereum/tests/crypto.rs
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
use ethereum_serai::crypto::*;
|
||||||
|
use frost::curve::Secp256k1;
|
||||||
|
use k256::{
|
||||||
|
elliptic_curve::{bigint::ArrayEncoding, ops::Reduce, sec1::ToEncodedPoint},
|
||||||
|
ProjectivePoint, Scalar, U256,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ecrecover() {
|
||||||
|
use k256::ecdsa::{
|
||||||
|
recoverable::Signature,
|
||||||
|
signature::{Signer, Verifier},
|
||||||
|
SigningKey, VerifyingKey,
|
||||||
|
};
|
||||||
|
use rand::rngs::OsRng;
|
||||||
|
|
||||||
|
let private = SigningKey::random(&mut OsRng);
|
||||||
|
let public = VerifyingKey::from(&private);
|
||||||
|
|
||||||
|
const MESSAGE: &'static [u8] = b"Hello, World!";
|
||||||
|
let sig: Signature = private.sign(MESSAGE);
|
||||||
|
public.verify(MESSAGE, &sig).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
ecrecover(hash_to_scalar(MESSAGE), sig.as_ref()[64], *sig.r(), *sig.s()).unwrap(),
|
||||||
|
address(&ProjectivePoint::from(public))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_signing() {
|
||||||
|
use frost::{
|
||||||
|
algorithm::Schnorr,
|
||||||
|
tests::{algorithm_machines, key_gen, sign},
|
||||||
|
};
|
||||||
|
use rand::rngs::OsRng;
|
||||||
|
|
||||||
|
let keys = key_gen::<_, Secp256k1>(&mut OsRng);
|
||||||
|
let _group_key = keys[&1].group_key();
|
||||||
|
|
||||||
|
const MESSAGE: &'static [u8] = b"Hello, World!";
|
||||||
|
|
||||||
|
let _sig = sign(
|
||||||
|
&mut OsRng,
|
||||||
|
algorithm_machines(&mut OsRng, Schnorr::<Secp256k1, EthereumHram>::new(), &keys),
|
||||||
|
MESSAGE,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ecrecover_hack() {
|
||||||
|
use frost::{
|
||||||
|
algorithm::Schnorr,
|
||||||
|
tests::{algorithm_machines, key_gen, sign},
|
||||||
|
};
|
||||||
|
use rand::rngs::OsRng;
|
||||||
|
|
||||||
|
let keys = key_gen::<_, Secp256k1>(&mut OsRng);
|
||||||
|
let group_key = keys[&1].group_key();
|
||||||
|
let group_key_encoded = group_key.to_encoded_point(true);
|
||||||
|
let group_key_compressed = group_key_encoded.as_ref();
|
||||||
|
let group_key_x = Scalar::from_uint_reduced(U256::from_be_slice(&group_key_compressed[1 .. 33]));
|
||||||
|
|
||||||
|
const MESSAGE: &'static [u8] = b"Hello, World!";
|
||||||
|
let hashed_message = keccak256(MESSAGE);
|
||||||
|
let chain_id = U256::from(Scalar::ONE);
|
||||||
|
|
||||||
|
let full_message = &[chain_id.to_be_byte_array().as_slice(), &hashed_message].concat();
|
||||||
|
|
||||||
|
let sig = sign(
|
||||||
|
&mut OsRng,
|
||||||
|
algorithm_machines(&mut OsRng, Schnorr::<Secp256k1, EthereumHram>::new(), &keys),
|
||||||
|
full_message,
|
||||||
|
);
|
||||||
|
|
||||||
|
let (sr, er) =
|
||||||
|
preprocess_signature_for_ecrecover(hashed_message, &sig.R, sig.s, &group_key, chain_id);
|
||||||
|
let q = ecrecover(sr, group_key_compressed[0] - 2, group_key_x, er).unwrap();
|
||||||
|
assert_eq!(q, address(&sig.R));
|
||||||
|
}
|
2
coins/ethereum/tests/mod.rs
Normal file
2
coins/ethereum/tests/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
mod contract;
|
||||||
|
mod crypto;
|
|
@ -18,14 +18,16 @@ hex = "0.4"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
|
||||||
curve25519-dalek = { version = "3", features = ["std"] }
|
sha3 = "0.10"
|
||||||
blake2 = "0.10"
|
blake2 = "0.10"
|
||||||
|
|
||||||
group = "0.12"
|
group = "0.12"
|
||||||
|
k256 = { version = "0.11", features = ["arithmetic", "keccak256", "ecdsa"] }
|
||||||
|
curve25519-dalek = { version = "3", features = ["std"] }
|
||||||
|
|
||||||
transcript = { package = "flexible-transcript", path = "../crypto/transcript", features = ["recommended"] }
|
transcript = { package = "flexible-transcript", path = "../crypto/transcript", features = ["recommended"] }
|
||||||
dalek-ff-group = { path = "../crypto/dalek-ff-group" }
|
dalek-ff-group = { path = "../crypto/dalek-ff-group" }
|
||||||
frost = { package = "modular-frost", path = "../crypto/frost" }
|
frost = { package = "modular-frost", path = "../crypto/frost", features = ["secp256k1", "ed25519"] }
|
||||||
|
|
||||||
monero = { version = "0.16", features = ["experimental"] }
|
monero = { version = "0.16", features = ["experimental"] }
|
||||||
monero-serai = { path = "../coins/monero", features = ["multisig"] }
|
monero-serai = { path = "../coins/monero", features = ["multisig"] }
|
||||||
|
|
Loading…
Reference in a new issue