mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-12 14:09:25 +00:00
Merge branch 'develop' into HEAD
Also updates monero-generators per comments in https://github.com/serai-dex/serai/pull/308.
This commit is contained in:
@@ -21,7 +21,7 @@ runs:
|
|||||||
using: "composite"
|
using: "composite"
|
||||||
steps:
|
steps:
|
||||||
- name: Install Protobuf
|
- name: Install Protobuf
|
||||||
uses: arduino/setup-protoc@master
|
uses: arduino/setup-protoc@v2.0.0
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ inputs.github-token }}
|
repo-token: ${{ inputs.github-token }}
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ runs:
|
|||||||
with:
|
with:
|
||||||
toolchain: ${{ inputs.rust-toolchain }}
|
toolchain: ${{ inputs.rust-toolchain }}
|
||||||
components: ${{ inputs.rust-components }}
|
components: ${{ inputs.rust-components }}
|
||||||
targets: wasm32-unknown-unknown
|
targets: wasm32-unknown-unknown, riscv32imac-unknown-none-elf
|
||||||
|
|
||||||
- name: Cache Rust
|
- name: Cache Rust
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
|
|||||||
2
.github/nightly-version
vendored
2
.github/nightly-version
vendored
@@ -1 +1 @@
|
|||||||
nightly-2023-05-01
|
nightly-2023-07-01
|
||||||
|
|||||||
2
.github/workflows/no-std.yml
vendored
2
.github/workflows/no-std.yml
vendored
@@ -18,4 +18,4 @@ jobs:
|
|||||||
github-token: ${{ inputs.github-token }}
|
github-token: ${{ inputs.github-token }}
|
||||||
|
|
||||||
- name: Verify no-std builds
|
- name: Verify no-std builds
|
||||||
run: cd tests/no-std && cargo build --target wasm32-unknown-unknown
|
run: cd tests/no-std && cargo build --target riscv32imac-unknown-none-elf
|
||||||
|
|||||||
2652
Cargo.lock
generated
2652
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -23,6 +23,8 @@ members = [
|
|||||||
"coins/monero/generators",
|
"coins/monero/generators",
|
||||||
"coins/monero",
|
"coins/monero",
|
||||||
|
|
||||||
|
"message-queue",
|
||||||
|
|
||||||
"processor/messages",
|
"processor/messages",
|
||||||
"processor",
|
"processor",
|
||||||
|
|
||||||
|
|||||||
@@ -12,48 +12,53 @@ all-features = true
|
|||||||
rustdoc-args = ["--cfg", "docsrs"]
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
futures = "0.3"
|
std-shims = { path = "../../common/std-shims", version = "0.1", default-features = false }
|
||||||
|
|
||||||
lazy_static = "1"
|
async-trait = { version = "0.1", default-features = false }
|
||||||
async-trait = "0.1"
|
thiserror = { version = "1", optional = true }
|
||||||
thiserror = "1"
|
|
||||||
|
|
||||||
rand_core = "0.6"
|
zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] }
|
||||||
rand_chacha = "0.3"
|
subtle = { version = "^2.4", default-features = false }
|
||||||
rand = "0.8"
|
|
||||||
rand_distr = "0.4"
|
|
||||||
|
|
||||||
zeroize = { version = "^1.5", features = ["zeroize_derive"] }
|
rand_core = { version = "0.6", default-features = false }
|
||||||
subtle = "^2.4"
|
# Used to send transactions
|
||||||
|
rand = { version = "0.8", default-features = false }
|
||||||
|
rand_chacha = { version = "0.3", default-features = false }
|
||||||
|
# Used to select decoys
|
||||||
|
rand_distr = { version = "0.4", default-features = false }
|
||||||
|
|
||||||
crc = "3"
|
crc = { version = "3", default-features = false }
|
||||||
sha3 = "0.10"
|
sha3 = { version = "0.10", default-features = false }
|
||||||
|
|
||||||
curve25519-dalek = { version = "^3.2", features = ["std"] }
|
curve25519-dalek = { version = "^3.2", default-features = false }
|
||||||
|
|
||||||
group = "0.13"
|
# Used for the hash to curve, along with the more complicated proofs
|
||||||
dalek-ff-group = { path = "../../crypto/dalek-ff-group", version = "0.3" }
|
group = { version = "0.13", default-features = false }
|
||||||
multiexp = { path = "../../crypto/multiexp", version = "0.3", features = ["batch"] }
|
dalek-ff-group = { path = "../../crypto/dalek-ff-group", version = "0.3", default-features = false }
|
||||||
|
multiexp = { path = "../../crypto/multiexp", version = "0.3", default-features = false, features = ["batch"] }
|
||||||
|
|
||||||
transcript = { package = "flexible-transcript", path = "../../crypto/transcript", version = "0.3", features = ["recommended"], optional = true }
|
# Needed for multisig
|
||||||
frost = { package = "modular-frost", path = "../../crypto/frost", version = "0.7", features = ["ed25519"], optional = true }
|
transcript = { package = "flexible-transcript", path = "../../crypto/transcript", version = "0.3", default-features = false, features = ["recommended"], optional = true }
|
||||||
dleq = { path = "../../crypto/dleq", version = "0.3", features = ["serialize"], optional = true }
|
dleq = { path = "../../crypto/dleq", version = "0.3", features = ["serialize"], optional = true }
|
||||||
|
frost = { package = "modular-frost", path = "../../crypto/frost", version = "0.7", features = ["ed25519"], optional = true }
|
||||||
|
|
||||||
monero-generators = { path = "generators", version = "0.3" }
|
monero-generators = { path = "generators", version = "0.3", default-features = false }
|
||||||
|
|
||||||
hex = "0.4"
|
futures = { version = "0.3", default-features = false, features = ["alloc"], optional = true }
|
||||||
serde = { version = "1", features = ["derive"] }
|
|
||||||
serde_json = "1"
|
|
||||||
|
|
||||||
base58-monero = "1"
|
hex = { version = "0.4", default-features = false, features = ["alloc"] }
|
||||||
monero-epee-bin-serde = "1"
|
serde = { version = "1", default-features = false, features = ["derive"] }
|
||||||
|
serde_json = { version = "1", default-features = false, features = ["alloc"] }
|
||||||
|
|
||||||
digest_auth = "0.3"
|
base58-monero = { version = "1", git = "https://github.com/monero-rs/base58-monero", rev = "5045e8d2b817b3b6c1190661f504e879bc769c29", default-features = false, features = ["check"] }
|
||||||
reqwest = { version = "0.11", features = ["json"] }
|
|
||||||
|
# Used for the provided RPC
|
||||||
|
digest_auth = { version = "0.3", optional = true }
|
||||||
|
reqwest = { version = "0.11", features = ["json"], optional = true }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
dalek-ff-group = { path = "../../crypto/dalek-ff-group", version = "0.3" }
|
dalek-ff-group = { path = "../../crypto/dalek-ff-group", version = "0.3", default-features = false }
|
||||||
monero-generators = { path = "generators", version = "0.3" }
|
monero-generators = { path = "generators", version = "0.3", default-features = false }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
hex-literal = "0.4"
|
hex-literal = "0.4"
|
||||||
@@ -64,4 +69,33 @@ monero-rpc = "0.3"
|
|||||||
frost = { package = "modular-frost", path = "../../crypto/frost", version = "0.7", features = ["tests"] }
|
frost = { package = "modular-frost", path = "../../crypto/frost", version = "0.7", features = ["tests"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
multisig = ["transcript", "frost", "dleq"]
|
std = [
|
||||||
|
"std-shims/std",
|
||||||
|
|
||||||
|
"thiserror",
|
||||||
|
|
||||||
|
"zeroize/std",
|
||||||
|
"subtle/std",
|
||||||
|
|
||||||
|
"rand_core/std",
|
||||||
|
"rand_chacha/std",
|
||||||
|
"rand/std",
|
||||||
|
"rand_distr/std",
|
||||||
|
|
||||||
|
"sha3/std",
|
||||||
|
|
||||||
|
"curve25519-dalek/std",
|
||||||
|
|
||||||
|
"multiexp/std",
|
||||||
|
|
||||||
|
"monero-generators/std",
|
||||||
|
|
||||||
|
"futures/std",
|
||||||
|
|
||||||
|
"hex/std",
|
||||||
|
"serde/std",
|
||||||
|
"serde_json/std",
|
||||||
|
]
|
||||||
|
http_rpc = ["digest_auth", "reqwest"]
|
||||||
|
multisig = ["transcript", "frost", "dleq", "std"]
|
||||||
|
default = ["std", "http_rpc"]
|
||||||
|
|||||||
@@ -41,15 +41,16 @@ fn generators(prefix: &'static str, path: &str) {
|
|||||||
.write_all(
|
.write_all(
|
||||||
format!(
|
format!(
|
||||||
"
|
"
|
||||||
lazy_static! {{
|
pub static GENERATORS_CELL: OnceLock<Generators> = OnceLock::new();
|
||||||
pub static ref GENERATORS: Generators = Generators {{
|
pub fn GENERATORS() -> &'static Generators {{
|
||||||
|
GENERATORS_CELL.get_or_init(|| Generators {{
|
||||||
G: [
|
G: [
|
||||||
{G_str}
|
{G_str}
|
||||||
],
|
],
|
||||||
H: [
|
H: [
|
||||||
{H_str}
|
{H_str}
|
||||||
],
|
],
|
||||||
}};
|
}})
|
||||||
}}
|
}}
|
||||||
",
|
",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ rustdoc-args = ["--cfg", "docsrs"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
std-shims = { path = "../../../common/std-shims", version = "0.1", default-features = false }
|
std-shims = { path = "../../../common/std-shims", version = "0.1", default-features = false }
|
||||||
|
|
||||||
lazy_static = "1"
|
|
||||||
|
|
||||||
subtle = { version = "^2.4", default-features = false }
|
subtle = { version = "^2.4", default-features = false }
|
||||||
|
|
||||||
sha3 = { version = "0.10", default-features = false }
|
sha3 = { version = "0.10", default-features = false }
|
||||||
@@ -26,6 +24,5 @@ group = { version = "0.13", default-features = false }
|
|||||||
dalek-ff-group = { path = "../../../crypto/dalek-ff-group", version = "0.3" }
|
dalek-ff-group = { path = "../../../crypto/dalek-ff-group", version = "0.3" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
alloc = ["lazy_static/spin_no_std"]
|
|
||||||
std = ["std-shims/std"]
|
std = ["std-shims/std"]
|
||||||
default = ["std"]
|
default = ["std"]
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
//! Generators used by Monero in both its Pedersen commitments and Bulletproofs(+).
|
//! Generators used by Monero in both its Pedersen commitments and Bulletproofs(+).
|
||||||
|
//!
|
||||||
//! An implementation of Monero's `ge_fromfe_frombytes_vartime`, simply called
|
//! An implementation of Monero's `ge_fromfe_frombytes_vartime`, simply called
|
||||||
//! `hash_to_point` here, is included, as needed to generate generators.
|
//! `hash_to_point` here, is included, as needed to generate generators.
|
||||||
|
|
||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
use core::cell::OnceCell;
|
||||||
|
use std_shims::sync::Mutex;
|
||||||
|
|
||||||
use sha3::{Digest, Keccak256};
|
use sha3::{Digest, Keccak256};
|
||||||
|
|
||||||
@@ -25,29 +27,33 @@ fn hash(data: &[u8]) -> [u8; 32] {
|
|||||||
Keccak256::digest(data).into()
|
Keccak256::digest(data).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
static H_CELL: OnceLock<DalekPoint> = OnceLock::new();
|
||||||
/// Monero alternate generator `H`, used for amounts in Pedersen commitments.
|
/// Monero's alternate generator `H`, used for amounts in Pedersen commitments.
|
||||||
pub static ref H: DalekPoint =
|
#[allow(non_snake_case)]
|
||||||
|
pub fn H() -> DalekPoint {
|
||||||
|
*H_CELL.get_or_init(|| {
|
||||||
CompressedEdwardsY(hash(&EdwardsPoint::generator().to_bytes()))
|
CompressedEdwardsY(hash(&EdwardsPoint::generator().to_bytes()))
|
||||||
.decompress()
|
.decompress()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.mul_by_cofactor();
|
.mul_by_cofactor()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Monero's `H` generator multiplied 2^i for each index, i.e. H, 2H, 4H, 8H, ...
|
static H_POW_2_CELL: OnceLock<[DalekPoint; 64]> = OnceLock::new();
|
||||||
/// used in old range proofs.
|
/// Monero's alternate generator `H`, multiplied by 2**i for i in 1 ..= 64.
|
||||||
/// https://github.com/monero-project/monero/blob/94e67bf96bbc010241f29ada6abc89f49a81759c/src/
|
#[allow(non_snake_case)]
|
||||||
/// ringct/rctTypes.h#L628
|
pub fn H_pow_2() -> &[DalekPoint; 64] {
|
||||||
pub static ref H2: [DalekPoint; 64] = generate_H2();
|
H_POW_2_CELL.get_or_init(|| {
|
||||||
|
let mut res = [H(); 64];
|
||||||
|
for i in 1 .. 64 {
|
||||||
|
res[i] = res[i - 1].double();
|
||||||
|
}
|
||||||
|
res
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn generate_H2() -> [DalekPoint; 64] {
|
fn generate_H2() -> [DalekPoint; 64] {
|
||||||
let mut temp = Vec::with_capacity(64);
|
|
||||||
for i in 0 .. 64 {
|
|
||||||
temp.push(Scalar::from(2_u128.pow(i)) * *H)
|
|
||||||
}
|
|
||||||
temp.try_into().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
const MAX_M: usize = 16;
|
const MAX_M: usize = 16;
|
||||||
const N: usize = 64;
|
const N: usize = 64;
|
||||||
@@ -67,7 +73,7 @@ pub fn bulletproofs_generators(dst: &'static [u8]) -> Generators {
|
|||||||
for i in 0 .. MAX_MN {
|
for i in 0 .. MAX_MN {
|
||||||
let i = 2 * i;
|
let i = 2 * i;
|
||||||
|
|
||||||
let mut even = H.compress().to_bytes().to_vec();
|
let mut even = H().compress().to_bytes().to_vec();
|
||||||
even.extend(dst);
|
even.extend(dst);
|
||||||
let mut odd = even.clone();
|
let mut odd = even.clone();
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
use std::io::{self, Read, Write};
|
use std_shims::{
|
||||||
|
vec::Vec,
|
||||||
|
io::{self, Read, Write},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
hash,
|
hash,
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
#![doc = include_str!("../README.md")]
|
#![doc = include_str!("../README.md")]
|
||||||
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
|
||||||
use std::io;
|
#[cfg(not(feature = "std"))]
|
||||||
|
#[macro_use]
|
||||||
|
extern crate alloc;
|
||||||
|
|
||||||
|
use std_shims::{sync::OnceLock, io};
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use rand_core::{RngCore, CryptoRng};
|
use rand_core::{RngCore, CryptoRng};
|
||||||
|
|
||||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||||
|
|
||||||
use sha3::{Digest, Keccak256};
|
use sha3::{Digest, Keccak256};
|
||||||
|
|
||||||
use curve25519_dalek::{
|
use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar, edwards::EdwardsPoint};
|
||||||
constants::ED25519_BASEPOINT_TABLE,
|
|
||||||
scalar::Scalar,
|
|
||||||
edwards::{EdwardsPoint, EdwardsBasepointTable},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use monero_generators::H;
|
pub use monero_generators::H;
|
||||||
|
|
||||||
@@ -37,6 +37,12 @@ pub mod wallet;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
|
static INV_EIGHT_CELL: OnceLock<Scalar> = OnceLock::new();
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub(crate) fn INV_EIGHT() -> Scalar {
|
||||||
|
*INV_EIGHT_CELL.get_or_init(|| Scalar::from(8u8).invert())
|
||||||
|
}
|
||||||
|
|
||||||
/// Monero protocol version. v15 is omitted as v15 was simply v14 and v16 being active at the same
|
/// Monero protocol version. v15 is omitted as v15 was simply v14 and v16 being active at the same
|
||||||
/// time, with regards to the transactions supported. Accordingly, v16 should be used during v15.
|
/// time, with regards to the transactions supported. Accordingly, v16 should be used during v15.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
||||||
@@ -107,10 +113,6 @@ impl Protocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref H_TABLE: EdwardsBasepointTable = EdwardsBasepointTable::create(&H);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Transparent structure representing a Pedersen commitment's contents.
|
/// Transparent structure representing a Pedersen commitment's contents.
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
|
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
|
||||||
@@ -131,7 +133,7 @@ impl Commitment {
|
|||||||
|
|
||||||
/// Calculate a Pedersen commitment, as a point, from the transparent structure.
|
/// Calculate a Pedersen commitment, as a point, from the transparent structure.
|
||||||
pub fn calculate(&self) -> EdwardsPoint {
|
pub fn calculate(&self) -> EdwardsPoint {
|
||||||
(&self.mask * &ED25519_BASEPOINT_TABLE) + (&Scalar::from(self.amount) * &*H_TABLE)
|
(&self.mask * &ED25519_BASEPOINT_TABLE) + (Scalar::from(self.amount) * H())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
// Required to be for this entire file, which isn't an issue, as it wouldn't bind to the static
|
use std_shims::{vec::Vec, sync::OnceLock};
|
||||||
#![allow(non_upper_case_globals)]
|
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use rand_core::{RngCore, CryptoRng};
|
use rand_core::{RngCore, CryptoRng};
|
||||||
|
|
||||||
use subtle::{Choice, ConditionallySelectable};
|
use subtle::{Choice, ConditionallySelectable};
|
||||||
@@ -15,13 +13,17 @@ use multiexp::multiexp as multiexp_const;
|
|||||||
|
|
||||||
pub(crate) use monero_generators::Generators;
|
pub(crate) use monero_generators::Generators;
|
||||||
|
|
||||||
use crate::{H as DALEK_H, Commitment, hash_to_scalar as dalek_hash};
|
use crate::{INV_EIGHT as DALEK_INV_EIGHT, H as DALEK_H, Commitment, hash_to_scalar as dalek_hash};
|
||||||
pub(crate) use crate::ringct::bulletproofs::scalar_vector::*;
|
pub(crate) use crate::ringct::bulletproofs::scalar_vector::*;
|
||||||
|
|
||||||
// Bring things into ff/group
|
#[inline]
|
||||||
lazy_static! {
|
pub(crate) fn INV_EIGHT() -> Scalar {
|
||||||
pub(crate) static ref INV_EIGHT: Scalar = Scalar::from(8u8).invert().unwrap();
|
Scalar(DALEK_INV_EIGHT())
|
||||||
pub(crate) static ref H: EdwardsPoint = EdwardsPoint(*DALEK_H);
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn H() -> EdwardsPoint {
|
||||||
|
EdwardsPoint(DALEK_H())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn hash_to_scalar(data: &[u8]) -> Scalar {
|
pub(crate) fn hash_to_scalar(data: &[u8]) -> Scalar {
|
||||||
@@ -34,7 +36,7 @@ pub(crate) const LOG_N: usize = 6; // 2 << 6 == N
|
|||||||
pub(crate) const N: usize = 64;
|
pub(crate) const N: usize = 64;
|
||||||
|
|
||||||
pub(crate) fn prove_multiexp(pairs: &[(Scalar, EdwardsPoint)]) -> EdwardsPoint {
|
pub(crate) fn prove_multiexp(pairs: &[(Scalar, EdwardsPoint)]) -> EdwardsPoint {
|
||||||
multiexp_const(pairs) * *INV_EIGHT
|
multiexp_const(pairs) * INV_EIGHT()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn vector_exponent(
|
pub(crate) fn vector_exponent(
|
||||||
@@ -91,7 +93,7 @@ pub(crate) fn bit_decompose(commitments: &[Commitment]) -> (ScalarVector, Scalar
|
|||||||
pub(crate) fn hash_commitments<C: IntoIterator<Item = DalekPoint>>(
|
pub(crate) fn hash_commitments<C: IntoIterator<Item = DalekPoint>>(
|
||||||
commitments: C,
|
commitments: C,
|
||||||
) -> (Scalar, Vec<EdwardsPoint>) {
|
) -> (Scalar, Vec<EdwardsPoint>) {
|
||||||
let V = commitments.into_iter().map(|c| EdwardsPoint(c) * *INV_EIGHT).collect::<Vec<_>>();
|
let V = commitments.into_iter().map(|c| EdwardsPoint(c) * INV_EIGHT()).collect::<Vec<_>>();
|
||||||
(hash_to_scalar(&V.iter().flat_map(|V| V.compress().to_bytes()).collect::<Vec<_>>()), V)
|
(hash_to_scalar(&V.iter().flat_map(|V| V.compress().to_bytes()).collect::<Vec<_>>()), V)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +104,7 @@ pub(crate) fn alpha_rho<R: RngCore + CryptoRng>(
|
|||||||
aR: &ScalarVector,
|
aR: &ScalarVector,
|
||||||
) -> (Scalar, EdwardsPoint) {
|
) -> (Scalar, EdwardsPoint) {
|
||||||
let ar = Scalar::random(rng);
|
let ar = Scalar::random(rng);
|
||||||
(ar, (vector_exponent(generators, aL, aR) + (EdwardsPoint::generator() * ar)) * *INV_EIGHT)
|
(ar, (vector_exponent(generators, aL, aR) + (EdwardsPoint::generator() * ar)) * INV_EIGHT())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn LR_statements(
|
pub(crate) fn LR_statements(
|
||||||
@@ -124,8 +126,9 @@ pub(crate) fn LR_statements(
|
|||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
static TWO_N_CELL: OnceLock<ScalarVector> = OnceLock::new();
|
||||||
pub(crate) static ref TWO_N: ScalarVector = ScalarVector::powers(Scalar::from(2u8), N);
|
pub(crate) fn TWO_N() -> &'static ScalarVector {
|
||||||
|
TWO_N_CELL.get_or_init(|| ScalarVector::powers(Scalar::from(2u8), N))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn challenge_products(w: &[Scalar], winv: &[Scalar]) -> Vec<Scalar> {
|
pub(crate) fn challenge_products(w: &[Scalar], winv: &[Scalar]) -> Vec<Scalar> {
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
use std::io::{self, Read, Write};
|
use std_shims::{
|
||||||
|
vec::Vec,
|
||||||
|
io::{self, Read, Write},
|
||||||
|
};
|
||||||
|
|
||||||
use rand_core::{RngCore, CryptoRng};
|
use rand_core::{RngCore, CryptoRng};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use lazy_static::lazy_static;
|
use std_shims::{vec::Vec, sync::OnceLock};
|
||||||
|
|
||||||
use rand_core::{RngCore, CryptoRng};
|
use rand_core::{RngCore, CryptoRng};
|
||||||
|
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
@@ -14,9 +15,9 @@ use crate::{Commitment, ringct::bulletproofs::core::*};
|
|||||||
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/generators.rs"));
|
include!(concat!(env!("OUT_DIR"), "/generators.rs"));
|
||||||
|
|
||||||
lazy_static! {
|
static IP12_CELL: OnceLock<Scalar> = OnceLock::new();
|
||||||
static ref ONE_N: ScalarVector = ScalarVector(vec![Scalar::ONE; N]);
|
pub(crate) fn IP12() -> Scalar {
|
||||||
static ref IP12: Scalar = inner_product(&ONE_N, &TWO_N);
|
*IP12_CELL.get_or_init(|| inner_product(&ScalarVector(vec![Scalar::ONE; N]), TWO_N()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
@@ -48,8 +49,9 @@ impl OriginalStruct {
|
|||||||
let (sL, sR) =
|
let (sL, sR) =
|
||||||
ScalarVector((0 .. (MN * 2)).map(|_| Scalar::random(&mut *rng)).collect::<Vec<_>>()).split();
|
ScalarVector((0 .. (MN * 2)).map(|_| Scalar::random(&mut *rng)).collect::<Vec<_>>()).split();
|
||||||
|
|
||||||
let (mut alpha, A) = alpha_rho(&mut *rng, &GENERATORS, &aL, &aR);
|
let generators = GENERATORS();
|
||||||
let (mut rho, S) = alpha_rho(&mut *rng, &GENERATORS, &sL, &sR);
|
let (mut alpha, A) = alpha_rho(&mut *rng, generators, &aL, &aR);
|
||||||
|
let (mut rho, S) = alpha_rho(&mut *rng, generators, &sL, &sR);
|
||||||
|
|
||||||
let y = hash_cache(&mut cache, &[A.compress().to_bytes(), S.compress().to_bytes()]);
|
let y = hash_cache(&mut cache, &[A.compress().to_bytes(), S.compress().to_bytes()]);
|
||||||
let mut cache = hash_to_scalar(&y.to_bytes());
|
let mut cache = hash_to_scalar(&y.to_bytes());
|
||||||
@@ -62,7 +64,7 @@ impl OriginalStruct {
|
|||||||
let zpow = ScalarVector::powers(z, M + 2);
|
let zpow = ScalarVector::powers(z, M + 2);
|
||||||
for j in 0 .. M {
|
for j in 0 .. M {
|
||||||
for i in 0 .. N {
|
for i in 0 .. N {
|
||||||
zero_twos.push(zpow[j + 2] * TWO_N[i]);
|
zero_twos.push(zpow[j + 2] * TWO_N()[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,8 +79,8 @@ impl OriginalStruct {
|
|||||||
let mut tau1 = Scalar::random(&mut *rng);
|
let mut tau1 = Scalar::random(&mut *rng);
|
||||||
let mut tau2 = Scalar::random(&mut *rng);
|
let mut tau2 = Scalar::random(&mut *rng);
|
||||||
|
|
||||||
let T1 = prove_multiexp(&[(t1, *H), (tau1, EdwardsPoint::generator())]);
|
let T1 = prove_multiexp(&[(t1, H()), (tau1, EdwardsPoint::generator())]);
|
||||||
let T2 = prove_multiexp(&[(t2, *H), (tau2, EdwardsPoint::generator())]);
|
let T2 = prove_multiexp(&[(t2, H()), (tau2, EdwardsPoint::generator())]);
|
||||||
|
|
||||||
let x =
|
let x =
|
||||||
hash_cache(&mut cache, &[z.to_bytes(), T1.compress().to_bytes(), T2.compress().to_bytes()]);
|
hash_cache(&mut cache, &[z.to_bytes(), T1.compress().to_bytes(), T2.compress().to_bytes()]);
|
||||||
@@ -112,10 +114,10 @@ impl OriginalStruct {
|
|||||||
let yinv = y.invert().unwrap();
|
let yinv = y.invert().unwrap();
|
||||||
let yinvpow = ScalarVector::powers(yinv, MN);
|
let yinvpow = ScalarVector::powers(yinv, MN);
|
||||||
|
|
||||||
let mut G_proof = GENERATORS.G[.. a.len()].to_vec();
|
let mut G_proof = generators.G[.. a.len()].to_vec();
|
||||||
let mut H_proof = GENERATORS.H[.. a.len()].to_vec();
|
let mut H_proof = generators.H[.. a.len()].to_vec();
|
||||||
H_proof.iter_mut().zip(yinvpow.0.iter()).for_each(|(this_H, yinvpow)| *this_H *= yinvpow);
|
H_proof.iter_mut().zip(yinvpow.0.iter()).for_each(|(this_H, yinvpow)| *this_H *= yinvpow);
|
||||||
let U = *H * x_ip;
|
let U = H() * x_ip;
|
||||||
|
|
||||||
let mut L = Vec::with_capacity(logMN);
|
let mut L = Vec::with_capacity(logMN);
|
||||||
let mut R = Vec::with_capacity(logMN);
|
let mut R = Vec::with_capacity(logMN);
|
||||||
@@ -230,10 +232,10 @@ impl OriginalStruct {
|
|||||||
let ip1y = ScalarVector::powers(y, M * N).sum();
|
let ip1y = ScalarVector::powers(y, M * N).sum();
|
||||||
let mut k = -(zpow[2] * ip1y);
|
let mut k = -(zpow[2] * ip1y);
|
||||||
for j in 1 ..= M {
|
for j in 1 ..= M {
|
||||||
k -= zpow[j + 2] * *IP12;
|
k -= zpow[j + 2] * IP12();
|
||||||
}
|
}
|
||||||
let y1 = Scalar(self.t) - ((z * ip1y) + k);
|
let y1 = Scalar(self.t) - ((z * ip1y) + k);
|
||||||
proof.push((-y1, *H));
|
proof.push((-y1, H()));
|
||||||
|
|
||||||
proof.push((-Scalar(self.taux), G));
|
proof.push((-Scalar(self.taux), G));
|
||||||
|
|
||||||
@@ -247,7 +249,7 @@ impl OriginalStruct {
|
|||||||
|
|
||||||
proof = Vec::with_capacity(4 + (2 * (MN + logMN)));
|
proof = Vec::with_capacity(4 + (2 * (MN + logMN)));
|
||||||
let z3 = (Scalar(self.t) - (Scalar(self.a) * Scalar(self.b))) * x_ip;
|
let z3 = (Scalar(self.t) - (Scalar(self.a) * Scalar(self.b))) * x_ip;
|
||||||
proof.push((z3, *H));
|
proof.push((z3, H()));
|
||||||
proof.push((-Scalar(self.mu), G));
|
proof.push((-Scalar(self.mu), G));
|
||||||
|
|
||||||
proof.push((Scalar::ONE, A));
|
proof.push((Scalar::ONE, A));
|
||||||
@@ -260,13 +262,14 @@ impl OriginalStruct {
|
|||||||
|
|
||||||
let w_cache = challenge_products(&w, &winv);
|
let w_cache = challenge_products(&w, &winv);
|
||||||
|
|
||||||
|
let generators = GENERATORS();
|
||||||
for i in 0 .. MN {
|
for i in 0 .. MN {
|
||||||
let g = (Scalar(self.a) * w_cache[i]) + z;
|
let g = (Scalar(self.a) * w_cache[i]) + z;
|
||||||
proof.push((-g, GENERATORS.G[i]));
|
proof.push((-g, generators.G[i]));
|
||||||
|
|
||||||
let mut h = Scalar(self.b) * yinvpow[i] * w_cache[(!i) & (MN - 1)];
|
let mut h = Scalar(self.b) * yinvpow[i] * w_cache[(!i) & (MN - 1)];
|
||||||
h -= ((zpow[(i / N) + 2] * TWO_N[i % N]) + (z * ypow[i])) * yinvpow[i];
|
h -= ((zpow[(i / N) + 2] * TWO_N()[i % N]) + (z * ypow[i])) * yinvpow[i];
|
||||||
proof.push((-h, GENERATORS.H[i]));
|
proof.push((-h, generators.H[i]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use lazy_static::lazy_static;
|
use std_shims::{vec::Vec, sync::OnceLock};
|
||||||
|
|
||||||
use rand_core::{RngCore, CryptoRng};
|
use rand_core::{RngCore, CryptoRng};
|
||||||
|
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
@@ -17,15 +18,17 @@ use crate::{
|
|||||||
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/generators_plus.rs"));
|
include!(concat!(env!("OUT_DIR"), "/generators_plus.rs"));
|
||||||
|
|
||||||
lazy_static! {
|
static TRANSCRIPT_CELL: OnceLock<[u8; 32]> = OnceLock::new();
|
||||||
static ref TRANSCRIPT: [u8; 32] =
|
pub(crate) fn TRANSCRIPT() -> [u8; 32] {
|
||||||
EdwardsPoint(raw_hash_to_point(hash(b"bulletproof_plus_transcript"))).compress().to_bytes();
|
*TRANSCRIPT_CELL.get_or_init(|| {
|
||||||
|
EdwardsPoint(raw_hash_to_point(hash(b"bulletproof_plus_transcript"))).compress().to_bytes()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TRANSCRIPT isn't a Scalar, so we need this alternative for the first hash
|
// TRANSCRIPT isn't a Scalar, so we need this alternative for the first hash
|
||||||
fn hash_plus<C: IntoIterator<Item = DalekPoint>>(commitments: C) -> (Scalar, Vec<EdwardsPoint>) {
|
fn hash_plus<C: IntoIterator<Item = DalekPoint>>(commitments: C) -> (Scalar, Vec<EdwardsPoint>) {
|
||||||
let (cache, commitments) = hash_commitments(commitments);
|
let (cache, commitments) = hash_commitments(commitments);
|
||||||
(hash_to_scalar(&[&*TRANSCRIPT as &[u8], &cache.to_bytes()].concat()), commitments)
|
(hash_to_scalar(&[TRANSCRIPT().as_ref(), &cache.to_bytes()].concat()), commitments)
|
||||||
}
|
}
|
||||||
|
|
||||||
// d[j*N+i] = z**(2*(j+1)) * 2**i
|
// d[j*N+i] = z**(2*(j+1)) * 2**i
|
||||||
@@ -34,7 +37,7 @@ fn d(z: Scalar, M: usize, MN: usize) -> (ScalarVector, ScalarVector) {
|
|||||||
let mut d = vec![Scalar::ZERO; MN];
|
let mut d = vec![Scalar::ZERO; MN];
|
||||||
for j in 0 .. M {
|
for j in 0 .. M {
|
||||||
for i in 0 .. N {
|
for i in 0 .. N {
|
||||||
d[(j * N) + i] = zpow[j] * TWO_N[i];
|
d[(j * N) + i] = zpow[j] * TWO_N()[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(zpow, ScalarVector(d))
|
(zpow, ScalarVector(d))
|
||||||
@@ -57,12 +60,14 @@ impl PlusStruct {
|
|||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
commitments: &[Commitment],
|
commitments: &[Commitment],
|
||||||
) -> PlusStruct {
|
) -> PlusStruct {
|
||||||
|
let generators = GENERATORS();
|
||||||
|
|
||||||
let (logMN, M, MN) = MN(commitments.len());
|
let (logMN, M, MN) = MN(commitments.len());
|
||||||
|
|
||||||
let (aL, aR) = bit_decompose(commitments);
|
let (aL, aR) = bit_decompose(commitments);
|
||||||
let commitments_points = commitments.iter().map(Commitment::calculate).collect::<Vec<_>>();
|
let commitments_points = commitments.iter().map(Commitment::calculate).collect::<Vec<_>>();
|
||||||
let (mut cache, _) = hash_plus(commitments_points.clone());
|
let (mut cache, _) = hash_plus(commitments_points.clone());
|
||||||
let (mut alpha1, A) = alpha_rho(&mut *rng, &GENERATORS, &aL, &aR);
|
let (mut alpha1, A) = alpha_rho(&mut *rng, generators, &aL, &aR);
|
||||||
|
|
||||||
let y = hash_cache(&mut cache, &[A.compress().to_bytes()]);
|
let y = hash_cache(&mut cache, &[A.compress().to_bytes()]);
|
||||||
let mut cache = hash_to_scalar(&y.to_bytes());
|
let mut cache = hash_to_scalar(&y.to_bytes());
|
||||||
@@ -87,8 +92,8 @@ impl PlusStruct {
|
|||||||
let yinv = y.invert().unwrap();
|
let yinv = y.invert().unwrap();
|
||||||
let yinvpow = ScalarVector::powers(yinv, MN);
|
let yinvpow = ScalarVector::powers(yinv, MN);
|
||||||
|
|
||||||
let mut G_proof = GENERATORS.G[.. a.len()].to_vec();
|
let mut G_proof = generators.G[.. a.len()].to_vec();
|
||||||
let mut H_proof = GENERATORS.H[.. a.len()].to_vec();
|
let mut H_proof = generators.H[.. a.len()].to_vec();
|
||||||
|
|
||||||
let mut L = Vec::with_capacity(logMN);
|
let mut L = Vec::with_capacity(logMN);
|
||||||
let mut R = Vec::with_capacity(logMN);
|
let mut R = Vec::with_capacity(logMN);
|
||||||
@@ -105,12 +110,12 @@ impl PlusStruct {
|
|||||||
let (G_L, G_R) = G_proof.split_at(aL.len());
|
let (G_L, G_R) = G_proof.split_at(aL.len());
|
||||||
let (H_L, H_R) = H_proof.split_at(aL.len());
|
let (H_L, H_R) = H_proof.split_at(aL.len());
|
||||||
|
|
||||||
let mut L_i = LR_statements(&(&aL * yinvpow[aL.len()]), G_R, &bR, H_L, cL, *H);
|
let mut L_i = LR_statements(&(&aL * yinvpow[aL.len()]), G_R, &bR, H_L, cL, H());
|
||||||
L_i.push((dL, G));
|
L_i.push((dL, G));
|
||||||
let L_i = prove_multiexp(&L_i);
|
let L_i = prove_multiexp(&L_i);
|
||||||
L.push(L_i);
|
L.push(L_i);
|
||||||
|
|
||||||
let mut R_i = LR_statements(&(&aR * ypow[aR.len()]), G_L, &bL, H_R, cR, *H);
|
let mut R_i = LR_statements(&(&aR * ypow[aR.len()]), G_L, &bL, H_R, cR, H());
|
||||||
R_i.push((dR, G));
|
R_i.push((dR, G));
|
||||||
let R_i = prove_multiexp(&R_i);
|
let R_i = prove_multiexp(&R_i);
|
||||||
R.push(R_i);
|
R.push(R_i);
|
||||||
@@ -139,9 +144,9 @@ impl PlusStruct {
|
|||||||
(r, G_proof[0]),
|
(r, G_proof[0]),
|
||||||
(s, H_proof[0]),
|
(s, H_proof[0]),
|
||||||
(d, G),
|
(d, G),
|
||||||
((r * y * b[0]) + (s * y * a[0]), *H),
|
((r * y * b[0]) + (s * y * a[0]), H()),
|
||||||
]);
|
]);
|
||||||
let B = prove_multiexp(&[(r * y * s, *H), (eta, G)]);
|
let B = prove_multiexp(&[(r * y * s, H()), (eta, G)]);
|
||||||
let e = hash_cache(&mut cache, &[A1.compress().to_bytes(), B.compress().to_bytes()]);
|
let e = hash_cache(&mut cache, &[A1.compress().to_bytes(), B.compress().to_bytes()]);
|
||||||
|
|
||||||
let r1 = (a[0] * e) + r;
|
let r1 = (a[0] * e) + r;
|
||||||
@@ -248,7 +253,7 @@ impl PlusStruct {
|
|||||||
let y_sum = weighted_powers(y, MN).sum();
|
let y_sum = weighted_powers(y, MN).sum();
|
||||||
proof.push((
|
proof.push((
|
||||||
Scalar(self.r1 * y.0 * self.s1) + (esq * ((yMNy * z * d_sum) + ((zsq - z) * y_sum))),
|
Scalar(self.r1 * y.0 * self.s1) + (esq * ((yMNy * z * d_sum) + ((zsq - z) * y_sum))),
|
||||||
*H,
|
H(),
|
||||||
));
|
));
|
||||||
|
|
||||||
let w_cache = challenge_products(&w, &winv);
|
let w_cache = challenge_products(&w, &winv);
|
||||||
@@ -259,11 +264,12 @@ impl PlusStruct {
|
|||||||
let minus_esq_z = -esq_z;
|
let minus_esq_z = -esq_z;
|
||||||
let mut minus_esq_y = minus_esq * yMN;
|
let mut minus_esq_y = minus_esq * yMN;
|
||||||
|
|
||||||
|
let generators = GENERATORS();
|
||||||
for i in 0 .. MN {
|
for i in 0 .. MN {
|
||||||
proof.push((e_r1_y * w_cache[i] + esq_z, GENERATORS.G[i]));
|
proof.push((e_r1_y * w_cache[i] + esq_z, generators.G[i]));
|
||||||
proof.push((
|
proof.push((
|
||||||
(e_s1 * w_cache[(!i) & (MN - 1)]) + minus_esq_z + (minus_esq_y * d[i]),
|
(e_s1 * w_cache[(!i) & (MN - 1)]) + minus_esq_z + (minus_esq_y * d[i]),
|
||||||
GENERATORS.H[i],
|
generators.H[i],
|
||||||
));
|
));
|
||||||
|
|
||||||
e_r1_y *= yinv;
|
e_r1_y *= yinv;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use core::ops::{Add, Sub, Mul, Index};
|
use core::ops::{Add, Sub, Mul, Index};
|
||||||
|
use std_shims::vec::Vec;
|
||||||
|
|
||||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
use core::ops::Deref;
|
use core::ops::Deref;
|
||||||
use std::io::{self, Read, Write};
|
use std_shims::{
|
||||||
|
vec::Vec,
|
||||||
|
io::{self, Read, Write},
|
||||||
|
};
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use thiserror::Error;
|
|
||||||
use rand_core::{RngCore, CryptoRng};
|
use rand_core::{RngCore, CryptoRng};
|
||||||
|
|
||||||
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
|
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
|
||||||
@@ -18,8 +19,8 @@ use curve25519_dalek::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Commitment, random_scalar, hash_to_scalar, wallet::decoys::Decoys, ringct::hash_to_point,
|
INV_EIGHT, Commitment, random_scalar, hash_to_scalar, wallet::decoys::Decoys,
|
||||||
serialize::*,
|
ringct::hash_to_point, serialize::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "multisig")]
|
#[cfg(feature = "multisig")]
|
||||||
@@ -29,28 +30,25 @@ pub use multisig::{ClsagDetails, ClsagAddendum, ClsagMultisig};
|
|||||||
#[cfg(feature = "multisig")]
|
#[cfg(feature = "multisig")]
|
||||||
pub(crate) use multisig::add_key_image_share;
|
pub(crate) use multisig::add_key_image_share;
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref INV_EIGHT: Scalar = Scalar::from(8u8).invert();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Errors returned when CLSAG signing fails.
|
/// Errors returned when CLSAG signing fails.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Error)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
|
#[cfg_attr(feature = "std", derive(thiserror::Error))]
|
||||||
pub enum ClsagError {
|
pub enum ClsagError {
|
||||||
#[error("internal error ({0})")]
|
#[cfg_attr(feature = "std", error("internal error ({0})"))]
|
||||||
InternalError(&'static str),
|
InternalError(&'static str),
|
||||||
#[error("invalid ring")]
|
#[cfg_attr(feature = "std", error("invalid ring"))]
|
||||||
InvalidRing,
|
InvalidRing,
|
||||||
#[error("invalid ring member (member {0}, ring size {1})")]
|
#[cfg_attr(feature = "std", error("invalid ring member (member {0}, ring size {1})"))]
|
||||||
InvalidRingMember(u8, u8),
|
InvalidRingMember(u8, u8),
|
||||||
#[error("invalid commitment")]
|
#[cfg_attr(feature = "std", error("invalid commitment"))]
|
||||||
InvalidCommitment,
|
InvalidCommitment,
|
||||||
#[error("invalid key image")]
|
#[cfg_attr(feature = "std", error("invalid key image"))]
|
||||||
InvalidImage,
|
InvalidImage,
|
||||||
#[error("invalid D")]
|
#[cfg_attr(feature = "std", error("invalid D"))]
|
||||||
InvalidD,
|
InvalidD,
|
||||||
#[error("invalid s")]
|
#[cfg_attr(feature = "std", error("invalid s"))]
|
||||||
InvalidS,
|
InvalidS,
|
||||||
#[error("invalid c1")]
|
#[cfg_attr(feature = "std", error("invalid c1"))]
|
||||||
InvalidC1,
|
InvalidC1,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +101,7 @@ fn core(
|
|||||||
let n = ring.len();
|
let n = ring.len();
|
||||||
|
|
||||||
let images_precomp = VartimeEdwardsPrecomputation::new([I, D]);
|
let images_precomp = VartimeEdwardsPrecomputation::new([I, D]);
|
||||||
let D = D * *INV_EIGHT;
|
let D = D * INV_EIGHT();
|
||||||
|
|
||||||
// Generate the transcript
|
// Generate the transcript
|
||||||
// Instead of generating multiple, a single transcript is created and then edited as needed
|
// Instead of generating multiple, a single transcript is created and then edited as needed
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
use core::{ops::Deref, fmt::Debug};
|
use core::{ops::Deref, fmt::Debug};
|
||||||
use std::{
|
use std_shims::io::{self, Read, Write};
|
||||||
io::{self, Read, Write},
|
use std::sync::{Arc, RwLock};
|
||||||
sync::{Arc, RwLock},
|
|
||||||
};
|
|
||||||
|
|
||||||
use rand_core::{RngCore, CryptoRng, SeedableRng};
|
use rand_core::{RngCore, CryptoRng, SeedableRng};
|
||||||
use rand_chacha::ChaCha20Rng;
|
use rand_chacha::ChaCha20Rng;
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
use core::ops::Deref;
|
use core::ops::Deref;
|
||||||
use std::io::{self, Read, Write};
|
use std_shims::{
|
||||||
|
vec::Vec,
|
||||||
|
io::{self, Read, Write},
|
||||||
|
};
|
||||||
|
|
||||||
use zeroize::Zeroizing;
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
|
|||||||
91
coins/monero/src/rpc/http.rs
Normal file
91
coins/monero/src/rpc/http.rs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
|
||||||
|
use digest_auth::AuthContext;
|
||||||
|
use reqwest::Client;
|
||||||
|
|
||||||
|
use crate::rpc::{RpcError, RpcConnection, Rpc};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct HttpRpc {
|
||||||
|
client: Client,
|
||||||
|
userpass: Option<(String, String)>,
|
||||||
|
url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HttpRpc {
|
||||||
|
/// Create a new HTTP(S) RPC connection.
|
||||||
|
///
|
||||||
|
/// A daemon requiring authentication can be used via including the username and password in the
|
||||||
|
/// URL.
|
||||||
|
pub fn new(mut url: String) -> Result<Rpc<HttpRpc>, RpcError> {
|
||||||
|
// Parse out the username and password
|
||||||
|
let userpass = if url.contains('@') {
|
||||||
|
let url_clone = url;
|
||||||
|
let split_url = url_clone.split('@').collect::<Vec<_>>();
|
||||||
|
if split_url.len() != 2 {
|
||||||
|
Err(RpcError::InvalidNode)?;
|
||||||
|
}
|
||||||
|
let mut userpass = split_url[0];
|
||||||
|
url = split_url[1].to_string();
|
||||||
|
|
||||||
|
// If there was additionally a protocol string, restore that to the daemon URL
|
||||||
|
if userpass.contains("://") {
|
||||||
|
let split_userpass = userpass.split("://").collect::<Vec<_>>();
|
||||||
|
if split_userpass.len() != 2 {
|
||||||
|
Err(RpcError::InvalidNode)?;
|
||||||
|
}
|
||||||
|
url = split_userpass[0].to_string() + "://" + &url;
|
||||||
|
userpass = split_userpass[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
let split_userpass = userpass.split(':').collect::<Vec<_>>();
|
||||||
|
if split_userpass.len() != 2 {
|
||||||
|
Err(RpcError::InvalidNode)?;
|
||||||
|
}
|
||||||
|
Some((split_userpass[0].to_string(), split_userpass[1].to_string()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Rpc(HttpRpc { client: Client::new(), userpass, url }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl RpcConnection for HttpRpc {
|
||||||
|
async fn post(&self, route: &str, body: Vec<u8>) -> Result<Vec<u8>, RpcError> {
|
||||||
|
let mut builder = self.client.post(self.url.clone() + "/" + route).body(body);
|
||||||
|
|
||||||
|
if let Some((user, pass)) = &self.userpass {
|
||||||
|
let req = self.client.post(&self.url).send().await.map_err(|_| RpcError::InvalidNode)?;
|
||||||
|
// Only provide authentication if this daemon actually expects it
|
||||||
|
if let Some(header) = req.headers().get("www-authenticate") {
|
||||||
|
builder = builder.header(
|
||||||
|
"Authorization",
|
||||||
|
digest_auth::parse(header.to_str().map_err(|_| RpcError::InvalidNode)?)
|
||||||
|
.map_err(|_| RpcError::InvalidNode)?
|
||||||
|
.respond(&AuthContext::new_post::<_, _, _, &[u8]>(
|
||||||
|
user,
|
||||||
|
pass,
|
||||||
|
"/".to_string() + route,
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
.map_err(|_| RpcError::InvalidNode)?
|
||||||
|
.to_header_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(
|
||||||
|
builder
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|_| RpcError::ConnectionError)?
|
||||||
|
.bytes()
|
||||||
|
.await
|
||||||
|
.map_err(|_| RpcError::ConnectionError)?
|
||||||
|
.slice(..)
|
||||||
|
.to_vec(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,23 +1,32 @@
|
|||||||
use std::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
use alloc::boxed::Box;
|
||||||
|
use std_shims::{
|
||||||
|
vec::Vec,
|
||||||
|
io,
|
||||||
|
string::{String, ToString},
|
||||||
|
};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
use curve25519_dalek::edwards::{EdwardsPoint, CompressedEdwardsY};
|
use curve25519_dalek::edwards::{EdwardsPoint, CompressedEdwardsY};
|
||||||
|
|
||||||
use serde::{Serialize, Deserialize, de::DeserializeOwned};
|
use serde::{Serialize, Deserialize, de::DeserializeOwned};
|
||||||
use serde_json::{Value, json};
|
use serde_json::{Value, json};
|
||||||
|
|
||||||
use digest_auth::AuthContext;
|
|
||||||
use reqwest::Client;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Protocol,
|
Protocol,
|
||||||
|
serialize::*,
|
||||||
transaction::{Input, Timelock, Transaction},
|
transaction::{Input, Timelock, Transaction},
|
||||||
block::Block,
|
block::Block,
|
||||||
wallet::Fee,
|
wallet::Fee,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "http_rpc")]
|
||||||
|
mod http;
|
||||||
|
#[cfg(feature = "http_rpc")]
|
||||||
|
pub use http::*;
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct EmptyResponse {}
|
pub struct EmptyResponse {}
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
@@ -38,23 +47,24 @@ struct TransactionsResponse {
|
|||||||
txs: Vec<TransactionResponse>,
|
txs: Vec<TransactionResponse>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, Error)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
#[cfg_attr(feature = "std", derive(thiserror::Error))]
|
||||||
pub enum RpcError {
|
pub enum RpcError {
|
||||||
#[error("internal error ({0})")]
|
#[cfg_attr(feature = "std", error("internal error ({0})"))]
|
||||||
InternalError(&'static str),
|
InternalError(&'static str),
|
||||||
#[error("connection error")]
|
#[cfg_attr(feature = "std", error("connection error"))]
|
||||||
ConnectionError,
|
ConnectionError,
|
||||||
#[error("invalid node")]
|
#[cfg_attr(feature = "std", error("invalid node"))]
|
||||||
InvalidNode,
|
InvalidNode,
|
||||||
#[error("unsupported protocol version ({0})")]
|
#[cfg_attr(feature = "std", error("unsupported protocol version ({0})"))]
|
||||||
UnsupportedProtocol(usize),
|
UnsupportedProtocol(usize),
|
||||||
#[error("transactions not found")]
|
#[cfg_attr(feature = "std", error("transactions not found"))]
|
||||||
TransactionsNotFound(Vec<[u8; 32]>),
|
TransactionsNotFound(Vec<[u8; 32]>),
|
||||||
#[error("invalid point ({0})")]
|
#[cfg_attr(feature = "std", error("invalid point ({0})"))]
|
||||||
InvalidPoint(String),
|
InvalidPoint(String),
|
||||||
#[error("pruned transaction")]
|
#[cfg_attr(feature = "std", error("pruned transaction"))]
|
||||||
PrunedTransaction,
|
PrunedTransaction,
|
||||||
#[error("invalid transaction ({0:?})")]
|
#[cfg_attr(feature = "std", error("invalid transaction ({0:?})"))]
|
||||||
InvalidTransaction([u8; 32]),
|
InvalidTransaction([u8; 32]),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,6 +84,23 @@ fn rpc_point(point: &str) -> Result<EdwardsPoint, RpcError> {
|
|||||||
.ok_or_else(|| RpcError::InvalidPoint(point.to_string()))
|
.ok_or_else(|| RpcError::InvalidPoint(point.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read an EPEE VarInt, distinct from the VarInts used throughout the rest of the protocol
|
||||||
|
fn read_epee_vi<R: io::Read>(reader: &mut R) -> io::Result<u64> {
|
||||||
|
let vi_start = read_byte(reader)?;
|
||||||
|
let len = match vi_start & 0b11 {
|
||||||
|
0 => 1,
|
||||||
|
1 => 2,
|
||||||
|
2 => 4,
|
||||||
|
3 => 8,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
let mut vi = u64::from(vi_start >> 2);
|
||||||
|
for i in 1 .. len {
|
||||||
|
vi |= u64::from(read_byte(reader)?) << (((i - 1) * 8) + 6);
|
||||||
|
}
|
||||||
|
Ok(vi)
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait RpcConnection: Clone + Debug {
|
pub trait RpcConnection: Clone + Debug {
|
||||||
/// Perform a POST request to the specified route with the specified body.
|
/// Perform a POST request to the specified route with the specified body.
|
||||||
@@ -82,91 +109,7 @@ pub trait RpcConnection: Clone + Debug {
|
|||||||
async fn post(&self, route: &str, body: Vec<u8>) -> Result<Vec<u8>, RpcError>;
|
async fn post(&self, route: &str, body: Vec<u8>) -> Result<Vec<u8>, RpcError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
// TODO: Make this provided methods for RpcConnection?
|
||||||
pub struct HttpRpc {
|
|
||||||
client: Client,
|
|
||||||
userpass: Option<(String, String)>,
|
|
||||||
url: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HttpRpc {
|
|
||||||
/// Create a new HTTP(S) RPC connection.
|
|
||||||
///
|
|
||||||
/// A daemon requiring authentication can be used via including the username and password in the
|
|
||||||
/// URL.
|
|
||||||
pub fn new(mut url: String) -> Result<Rpc<HttpRpc>, RpcError> {
|
|
||||||
// Parse out the username and password
|
|
||||||
let userpass = if url.contains('@') {
|
|
||||||
let url_clone = url;
|
|
||||||
let split_url = url_clone.split('@').collect::<Vec<_>>();
|
|
||||||
if split_url.len() != 2 {
|
|
||||||
Err(RpcError::InvalidNode)?;
|
|
||||||
}
|
|
||||||
let mut userpass = split_url[0];
|
|
||||||
url = split_url[1].to_string();
|
|
||||||
|
|
||||||
// If there was additionally a protocol string, restore that to the daemon URL
|
|
||||||
if userpass.contains("://") {
|
|
||||||
let split_userpass = userpass.split("://").collect::<Vec<_>>();
|
|
||||||
if split_userpass.len() != 2 {
|
|
||||||
Err(RpcError::InvalidNode)?;
|
|
||||||
}
|
|
||||||
url = split_userpass[0].to_string() + "://" + &url;
|
|
||||||
userpass = split_userpass[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
let split_userpass = userpass.split(':').collect::<Vec<_>>();
|
|
||||||
if split_userpass.len() != 2 {
|
|
||||||
Err(RpcError::InvalidNode)?;
|
|
||||||
}
|
|
||||||
Some((split_userpass[0].to_string(), split_userpass[1].to_string()))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Rpc(HttpRpc { client: Client::new(), userpass, url }))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl RpcConnection for HttpRpc {
|
|
||||||
async fn post(&self, route: &str, body: Vec<u8>) -> Result<Vec<u8>, RpcError> {
|
|
||||||
let mut builder = self.client.post(self.url.clone() + "/" + route).body(body);
|
|
||||||
|
|
||||||
if let Some((user, pass)) = &self.userpass {
|
|
||||||
let req = self.client.post(&self.url).send().await.map_err(|_| RpcError::InvalidNode)?;
|
|
||||||
// Only provide authentication if this daemon actually expects it
|
|
||||||
if let Some(header) = req.headers().get("www-authenticate") {
|
|
||||||
builder = builder.header(
|
|
||||||
"Authorization",
|
|
||||||
digest_auth::parse(header.to_str().map_err(|_| RpcError::InvalidNode)?)
|
|
||||||
.map_err(|_| RpcError::InvalidNode)?
|
|
||||||
.respond(&AuthContext::new_post::<_, _, _, &[u8]>(
|
|
||||||
user,
|
|
||||||
pass,
|
|
||||||
"/".to_string() + route,
|
|
||||||
None,
|
|
||||||
))
|
|
||||||
.map_err(|_| RpcError::InvalidNode)?
|
|
||||||
.to_header_string(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(
|
|
||||||
builder
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.map_err(|_| RpcError::ConnectionError)?
|
|
||||||
.bytes()
|
|
||||||
.await
|
|
||||||
.map_err(|_| RpcError::ConnectionError)?
|
|
||||||
.slice(..)
|
|
||||||
.to_vec(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Rpc<R: RpcConnection>(R);
|
pub struct Rpc<R: RpcConnection>(R);
|
||||||
impl<R: RpcConnection> Rpc<R> {
|
impl<R: RpcConnection> Rpc<R> {
|
||||||
@@ -179,10 +122,9 @@ impl<R: RpcConnection> Rpc<R> {
|
|||||||
route: &str,
|
route: &str,
|
||||||
params: Option<Params>,
|
params: Option<Params>,
|
||||||
) -> Result<Response, RpcError> {
|
) -> Result<Response, RpcError> {
|
||||||
self
|
serde_json::from_str(
|
||||||
.call_tail(
|
std_shims::str::from_utf8(
|
||||||
route,
|
&self
|
||||||
self
|
|
||||||
.0
|
.0
|
||||||
.post(
|
.post(
|
||||||
route,
|
route,
|
||||||
@@ -194,7 +136,9 @@ impl<R: RpcConnection> Rpc<R> {
|
|||||||
)
|
)
|
||||||
.await?,
|
.await?,
|
||||||
)
|
)
|
||||||
.await
|
.map_err(|_| RpcError::InvalidNode)?,
|
||||||
|
)
|
||||||
|
.map_err(|_| RpcError::InternalError("Failed to parse JSON response"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform a JSON-RPC call with the specified method with the provided parameters
|
/// Perform a JSON-RPC call with the specified method with the provided parameters
|
||||||
@@ -211,26 +155,8 @@ impl<R: RpcConnection> Rpc<R> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Perform a binary call to the specified route with the provided parameters.
|
/// Perform a binary call to the specified route with the provided parameters.
|
||||||
pub async fn bin_call<Response: DeserializeOwned + Debug>(
|
pub async fn bin_call(&self, route: &str, params: Vec<u8>) -> Result<Vec<u8>, RpcError> {
|
||||||
&self,
|
self.0.post(route, params).await
|
||||||
route: &str,
|
|
||||||
params: Vec<u8>,
|
|
||||||
) -> Result<Response, RpcError> {
|
|
||||||
self.call_tail(route, self.0.post(route, params).await?).await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn call_tail<Response: DeserializeOwned + Debug>(
|
|
||||||
&self,
|
|
||||||
route: &str,
|
|
||||||
res: Vec<u8>,
|
|
||||||
) -> Result<Response, RpcError> {
|
|
||||||
Ok(if !route.ends_with(".bin") {
|
|
||||||
serde_json::from_str(std::str::from_utf8(&res).map_err(|_| RpcError::InvalidNode)?)
|
|
||||||
.map_err(|_| RpcError::InternalError("Failed to parse JSON response"))?
|
|
||||||
} else {
|
|
||||||
monero_epee_bin_serde::from_bytes(&res)
|
|
||||||
.map_err(|_| RpcError::InternalError("Failed to parse binary response"))?
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the active blockchain protocol version.
|
/// Get the active blockchain protocol version.
|
||||||
@@ -391,6 +317,9 @@ impl<R: RpcConnection> Rpc<R> {
|
|||||||
|
|
||||||
/// Get the output indexes of the specified transaction.
|
/// Get the output indexes of the specified transaction.
|
||||||
pub async fn get_o_indexes(&self, hash: [u8; 32]) -> Result<Vec<u64>, RpcError> {
|
pub async fn get_o_indexes(&self, hash: [u8; 32]) -> Result<Vec<u64>, RpcError> {
|
||||||
|
/*
|
||||||
|
TODO: Use these when a suitable epee serde lib exists
|
||||||
|
|
||||||
#[derive(Serialize, Debug)]
|
#[derive(Serialize, Debug)]
|
||||||
struct Request {
|
struct Request {
|
||||||
txid: [u8; 32],
|
txid: [u8; 32],
|
||||||
@@ -400,20 +329,125 @@ impl<R: RpcConnection> Rpc<R> {
|
|||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
struct OIndexes {
|
struct OIndexes {
|
||||||
o_indexes: Vec<u64>,
|
o_indexes: Vec<u64>,
|
||||||
status: String,
|
|
||||||
untrusted: bool,
|
|
||||||
credits: usize,
|
|
||||||
top_hash: String,
|
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
let indexes: OIndexes = self
|
// Given the immaturity of Rust epee libraries, this is a homegrown one which is only validated
|
||||||
.bin_call(
|
// to work against this specific function
|
||||||
"get_o_indexes.bin",
|
|
||||||
monero_epee_bin_serde::to_bytes(&Request { txid: hash }).unwrap(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(indexes.o_indexes)
|
// Header for EPEE, an 8-byte magic and a version
|
||||||
|
const EPEE_HEADER: &[u8] = b"\x01\x11\x01\x01\x01\x01\x02\x01\x01";
|
||||||
|
|
||||||
|
let mut request = EPEE_HEADER.to_vec();
|
||||||
|
// Number of fields (shifted over 2 bits as the 2 LSBs are reserved for metadata)
|
||||||
|
request.push(1 << 2);
|
||||||
|
// Length of field name
|
||||||
|
request.push(4);
|
||||||
|
// Field name
|
||||||
|
request.extend(b"txid");
|
||||||
|
// Type of field
|
||||||
|
request.push(10);
|
||||||
|
// Length of string, since this byte array is technically a string
|
||||||
|
request.push(32 << 2);
|
||||||
|
// The "string"
|
||||||
|
request.extend(hash);
|
||||||
|
|
||||||
|
let indexes_buf = self.bin_call("get_o_indexes.bin", request).await?;
|
||||||
|
let mut indexes: &[u8] = indexes_buf.as_ref();
|
||||||
|
|
||||||
|
(|| {
|
||||||
|
if read_bytes::<_, { EPEE_HEADER.len() }>(&mut indexes)? != EPEE_HEADER {
|
||||||
|
Err(io::Error::new(io::ErrorKind::Other, "invalid header"))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let read_object = |reader: &mut &[u8]| {
|
||||||
|
let fields = read_byte(reader)? >> 2;
|
||||||
|
|
||||||
|
for _ in 0 .. fields {
|
||||||
|
let name_len = read_byte(reader)?;
|
||||||
|
let name = read_raw_vec(read_byte, name_len.into(), reader)?;
|
||||||
|
|
||||||
|
let type_with_array_flag = read_byte(reader)?;
|
||||||
|
let kind = type_with_array_flag & (!0x80);
|
||||||
|
|
||||||
|
let iters = if type_with_array_flag != kind { read_epee_vi(reader)? } else { 1 };
|
||||||
|
|
||||||
|
if (&name == b"o_indexes") && (kind != 5) {
|
||||||
|
Err(io::Error::new(io::ErrorKind::Other, "o_indexes weren't u64s"))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let f = match kind {
|
||||||
|
// i64
|
||||||
|
1 => |reader: &mut &[u8]| read_raw_vec(read_byte, 8, reader),
|
||||||
|
// i32
|
||||||
|
2 => |reader: &mut &[u8]| read_raw_vec(read_byte, 4, reader),
|
||||||
|
// i16
|
||||||
|
3 => |reader: &mut &[u8]| read_raw_vec(read_byte, 2, reader),
|
||||||
|
// i8
|
||||||
|
4 => |reader: &mut &[u8]| read_raw_vec(read_byte, 1, reader),
|
||||||
|
// u64
|
||||||
|
5 => |reader: &mut &[u8]| read_raw_vec(read_byte, 8, reader),
|
||||||
|
// u32
|
||||||
|
6 => |reader: &mut &[u8]| read_raw_vec(read_byte, 4, reader),
|
||||||
|
// u16
|
||||||
|
7 => |reader: &mut &[u8]| read_raw_vec(read_byte, 2, reader),
|
||||||
|
// u8
|
||||||
|
8 => |reader: &mut &[u8]| read_raw_vec(read_byte, 1, reader),
|
||||||
|
// double
|
||||||
|
9 => |reader: &mut &[u8]| read_raw_vec(read_byte, 8, reader),
|
||||||
|
// string, or any collection of bytes
|
||||||
|
10 => |reader: &mut &[u8]| {
|
||||||
|
let len = read_epee_vi(reader)?;
|
||||||
|
read_raw_vec(
|
||||||
|
read_byte,
|
||||||
|
len
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| io::Error::new(io::ErrorKind::Other, "u64 length exceeded usize"))?,
|
||||||
|
reader,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
// bool
|
||||||
|
11 => |reader: &mut &[u8]| read_raw_vec(read_byte, 1, reader),
|
||||||
|
// object, errors here as it shouldn't be used on this call
|
||||||
|
12 => |_: &mut &[u8]| {
|
||||||
|
Err(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
"node used object in reply to get_o_indexes",
|
||||||
|
))
|
||||||
|
},
|
||||||
|
// array, so far unused
|
||||||
|
13 => |_: &mut &[u8]| {
|
||||||
|
Err(io::Error::new(io::ErrorKind::Other, "node used the unused array type"))
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
|_: &mut &[u8]| Err(io::Error::new(io::ErrorKind::Other, "node used an invalid type"))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut res = vec![];
|
||||||
|
for _ in 0 .. iters {
|
||||||
|
res.push(f(reader)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut actual_res = Vec::with_capacity(res.len());
|
||||||
|
if &name == b"o_indexes" {
|
||||||
|
for o_index in res {
|
||||||
|
actual_res.push(u64::from_le_bytes(o_index.try_into().map_err(|_| {
|
||||||
|
io::Error::new(io::ErrorKind::Other, "node didn't provide 8 bytes for a u64")
|
||||||
|
})?));
|
||||||
|
}
|
||||||
|
return Ok(actual_res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Didn't return a response with o_indexes
|
||||||
|
// TODO: Check if this didn't have o_indexes because it's an error response
|
||||||
|
Err(io::Error::new(io::ErrorKind::Other, "response didn't contain o_indexes"))
|
||||||
|
};
|
||||||
|
|
||||||
|
read_object(&mut indexes)
|
||||||
|
})()
|
||||||
|
.map_err(|_| RpcError::InvalidNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the output distribution, from the specified height to the specified height (both
|
/// Get the output distribution, from the specified height to the specified height (both
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
use std::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
use std::io::{self, Read, Write};
|
use std_shims::{
|
||||||
|
vec::Vec,
|
||||||
|
io::{self, Read, Write},
|
||||||
|
};
|
||||||
|
|
||||||
use curve25519_dalek::{
|
use curve25519_dalek::{
|
||||||
scalar::Scalar,
|
scalar::Scalar,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use hex_literal::hex;
|
use hex_literal::hex;
|
||||||
use rand::rngs::OsRng;
|
use rand_core::OsRng;
|
||||||
|
|
||||||
use curve25519_dalek::{scalar::Scalar, edwards::CompressedEdwardsY};
|
use curve25519_dalek::{scalar::Scalar, edwards::CompressedEdwardsY};
|
||||||
use multiexp::BatchVerifier;
|
use multiexp::BatchVerifier;
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
use core::cmp::Ordering;
|
use core::cmp::Ordering;
|
||||||
use std::io::{self, Read, Write};
|
use std_shims::{
|
||||||
|
vec::Vec,
|
||||||
|
io::{self, Read, Write},
|
||||||
|
};
|
||||||
|
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
@@ -17,7 +20,7 @@ use crate::{
|
|||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub enum Input {
|
pub enum Input {
|
||||||
Gen(u64),
|
Gen(u64),
|
||||||
ToKey { amount: u64, key_offsets: Vec<u64>, key_image: EdwardsPoint },
|
ToKey { amount: Option<u64>, key_offsets: Vec<u64>, key_image: EdwardsPoint },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Input {
|
impl Input {
|
||||||
@@ -37,7 +40,7 @@ impl Input {
|
|||||||
|
|
||||||
Input::ToKey { amount, key_offsets, key_image } => {
|
Input::ToKey { amount, key_offsets, key_image } => {
|
||||||
w.write_all(&[2])?;
|
w.write_all(&[2])?;
|
||||||
write_varint(amount, w)?;
|
write_varint(&amount.unwrap_or(0), w)?;
|
||||||
write_vec(write_varint, key_offsets, w)?;
|
write_vec(write_varint, key_offsets, w)?;
|
||||||
write_point(key_image, w)
|
write_point(key_image, w)
|
||||||
}
|
}
|
||||||
@@ -50,14 +53,18 @@ impl Input {
|
|||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read<R: Read>(r: &mut R) -> io::Result<Input> {
|
pub fn read<R: Read>(interpret_as_rct: bool, r: &mut R) -> io::Result<Input> {
|
||||||
Ok(match read_byte(r)? {
|
Ok(match read_byte(r)? {
|
||||||
255 => Input::Gen(read_varint(r)?),
|
255 => Input::Gen(read_varint(r)?),
|
||||||
2 => Input::ToKey {
|
2 => {
|
||||||
amount: read_varint(r)?,
|
let amount = read_varint(r)?;
|
||||||
key_offsets: read_vec(read_varint, r)?,
|
let amount = if (amount == 0) && interpret_as_rct { None } else { Some(amount) };
|
||||||
key_image: read_torsion_free_point(r)?,
|
Input::ToKey {
|
||||||
},
|
amount,
|
||||||
|
key_offsets: read_vec(read_varint, r)?,
|
||||||
|
key_image: read_torsion_free_point(r)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
Err(io::Error::new(io::ErrorKind::Other, "Tried to deserialize unknown/unused input type"))?
|
Err(io::Error::new(io::ErrorKind::Other, "Tried to deserialize unknown/unused input type"))?
|
||||||
}
|
}
|
||||||
@@ -68,7 +75,7 @@ impl Input {
|
|||||||
// Doesn't bother moving to an enum for the unused Script classes
|
// Doesn't bother moving to an enum for the unused Script classes
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub struct Output {
|
pub struct Output {
|
||||||
pub amount: u64,
|
pub amount: Option<u64>,
|
||||||
pub key: CompressedEdwardsY,
|
pub key: CompressedEdwardsY,
|
||||||
pub view_tag: Option<u8>,
|
pub view_tag: Option<u8>,
|
||||||
}
|
}
|
||||||
@@ -79,7 +86,7 @@ impl Output {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||||
write_varint(&self.amount, w)?;
|
write_varint(&self.amount.unwrap_or(0), w)?;
|
||||||
w.write_all(&[2 + u8::from(self.view_tag.is_some())])?;
|
w.write_all(&[2 + u8::from(self.view_tag.is_some())])?;
|
||||||
w.write_all(&self.key.to_bytes())?;
|
w.write_all(&self.key.to_bytes())?;
|
||||||
if let Some(view_tag) = self.view_tag {
|
if let Some(view_tag) = self.view_tag {
|
||||||
@@ -94,8 +101,17 @@ impl Output {
|
|||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read<R: Read>(r: &mut R) -> io::Result<Output> {
|
pub fn read<R: Read>(interpret_as_rct: bool, r: &mut R) -> io::Result<Output> {
|
||||||
let amount = read_varint(r)?;
|
let amount = read_varint(r)?;
|
||||||
|
let amount = if interpret_as_rct {
|
||||||
|
if amount != 0 {
|
||||||
|
Err(io::Error::new(io::ErrorKind::Other, "RCT TX output wasn't 0"))?;
|
||||||
|
}
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(amount)
|
||||||
|
};
|
||||||
|
|
||||||
let view_tag = match read_byte(r)? {
|
let view_tag = match read_byte(r)? {
|
||||||
2 => false,
|
2 => false,
|
||||||
3 => true,
|
3 => true,
|
||||||
@@ -191,11 +207,25 @@ impl TransactionPrefix {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn read<R: Read>(r: &mut R) -> io::Result<TransactionPrefix> {
|
pub fn read<R: Read>(r: &mut R) -> io::Result<TransactionPrefix> {
|
||||||
|
let version = read_varint(r)?;
|
||||||
|
// TODO: Create an enum out of version
|
||||||
|
if (version == 0) || (version > 2) {
|
||||||
|
Err(io::Error::new(io::ErrorKind::Other, "unrecognized transaction version"))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let timelock = Timelock::from_raw(read_varint(r)?);
|
||||||
|
|
||||||
|
let inputs = read_vec(|r| Input::read(version == 2, r), r)?;
|
||||||
|
if inputs.is_empty() {
|
||||||
|
Err(io::Error::new(io::ErrorKind::Other, "transaction had no inputs"))?;
|
||||||
|
}
|
||||||
|
let is_miner_tx = matches!(inputs[0], Input::Gen { .. });
|
||||||
|
|
||||||
let mut prefix = TransactionPrefix {
|
let mut prefix = TransactionPrefix {
|
||||||
version: read_varint(r)?,
|
version,
|
||||||
timelock: Timelock::from_raw(read_varint(r)?),
|
timelock,
|
||||||
inputs: read_vec(Input::read, r)?,
|
inputs,
|
||||||
outputs: read_vec(Output::read, r)?,
|
outputs: read_vec(|r| Output::read((!is_miner_tx) && (version == 2), r), r)?,
|
||||||
extra: vec![],
|
extra: vec![],
|
||||||
};
|
};
|
||||||
prefix.extra = read_vec(read_byte, r)?;
|
prefix.extra = read_vec(read_byte, r)?;
|
||||||
@@ -277,10 +307,10 @@ impl Transaction {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|input| match input {
|
.map(|input| match input {
|
||||||
Input::Gen(..) => 0,
|
Input::Gen(..) => 0,
|
||||||
Input::ToKey { amount, .. } => *amount,
|
Input::ToKey { amount, .. } => amount.unwrap(),
|
||||||
})
|
})
|
||||||
.sum::<u64>()
|
.sum::<u64>()
|
||||||
.saturating_sub(prefix.outputs.iter().map(|output| output.amount).sum());
|
.saturating_sub(prefix.outputs.iter().map(|output| output.amount.unwrap()).sum());
|
||||||
} else if prefix.version == 2 {
|
} else if prefix.version == 2 {
|
||||||
rct_signatures = RctSignatures::read(
|
rct_signatures = RctSignatures::read(
|
||||||
prefix
|
prefix
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
use core::{marker::PhantomData, fmt::Debug};
|
use core::{marker::PhantomData, fmt::Debug};
|
||||||
use std::string::ToString;
|
use std_shims::string::{String, ToString};
|
||||||
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
@@ -114,19 +112,20 @@ impl<B: AddressBytes> Zeroize for AddressMeta<B> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Error when decoding an address.
|
/// Error when decoding an address.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Error)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
|
#[cfg_attr(feature = "std", derive(thiserror::Error))]
|
||||||
pub enum AddressError {
|
pub enum AddressError {
|
||||||
#[error("invalid address byte")]
|
#[cfg_attr(feature = "std", error("invalid address byte"))]
|
||||||
InvalidByte,
|
InvalidByte,
|
||||||
#[error("invalid address encoding")]
|
#[cfg_attr(feature = "std", error("invalid address encoding"))]
|
||||||
InvalidEncoding,
|
InvalidEncoding,
|
||||||
#[error("invalid length")]
|
#[cfg_attr(feature = "std", error("invalid length"))]
|
||||||
InvalidLength,
|
InvalidLength,
|
||||||
#[error("invalid key")]
|
#[cfg_attr(feature = "std", error("invalid key"))]
|
||||||
InvalidKey,
|
InvalidKey,
|
||||||
#[error("unknown features")]
|
#[cfg_attr(feature = "std", error("unknown features"))]
|
||||||
UnknownFeatures,
|
UnknownFeatures,
|
||||||
#[error("different network than expected")]
|
#[cfg_attr(feature = "std", error("different network than expected"))]
|
||||||
DifferentNetwork,
|
DifferentNetwork,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
use std::collections::HashSet;
|
use std_shims::{sync::OnceLock, vec::Vec, collections::HashSet};
|
||||||
|
|
||||||
use futures::lock::{Mutex, MutexGuard};
|
#[cfg(not(feature = "std"))]
|
||||||
|
use std_shims::sync::Mutex;
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
use futures::lock::Mutex;
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||||
|
|
||||||
use rand_core::{RngCore, CryptoRng};
|
use rand_core::{RngCore, CryptoRng};
|
||||||
use rand_distr::{Distribution, Gamma};
|
use rand_distr::{Distribution, Gamma};
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
use rand_distr::num_traits::Float;
|
||||||
|
|
||||||
use curve25519_dalek::edwards::EdwardsPoint;
|
use curve25519_dalek::edwards::EdwardsPoint;
|
||||||
|
|
||||||
@@ -23,18 +26,19 @@ const BLOCK_TIME: usize = 120;
|
|||||||
const BLOCKS_PER_YEAR: usize = 365 * 24 * 60 * 60 / BLOCK_TIME;
|
const BLOCKS_PER_YEAR: usize = 365 * 24 * 60 * 60 / BLOCK_TIME;
|
||||||
const TIP_APPLICATION: f64 = (LOCK_WINDOW * BLOCK_TIME) as f64;
|
const TIP_APPLICATION: f64 = (LOCK_WINDOW * BLOCK_TIME) as f64;
|
||||||
|
|
||||||
lazy_static! {
|
// TODO: Expose an API to reset this in case a reorg occurs/the RPC fails/returns garbage
|
||||||
static ref GAMMA: Gamma<f64> = Gamma::new(19.28, 1.0 / 1.61).unwrap();
|
// TODO: Update this when scanning a block, as possible
|
||||||
// TODO: Expose an API to reset this in case a reorg occurs/the RPC fails/returns garbage
|
static DISTRIBUTION_CELL: OnceLock<Mutex<Vec<u64>>> = OnceLock::new();
|
||||||
// TODO: Update this when scanning a block, as possible
|
#[allow(non_snake_case)]
|
||||||
static ref DISTRIBUTION: Mutex<Vec<u64>> = Mutex::new(Vec::with_capacity(3000000));
|
fn DISTRIBUTION() -> &'static Mutex<Vec<u64>> {
|
||||||
|
DISTRIBUTION_CELL.get_or_init(|| Mutex::new(Vec::with_capacity(3000000)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn select_n<'a, R: RngCore + CryptoRng, RPC: RpcConnection>(
|
async fn select_n<'a, R: RngCore + CryptoRng, RPC: RpcConnection>(
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
rpc: &Rpc<RPC>,
|
rpc: &Rpc<RPC>,
|
||||||
distribution: &MutexGuard<'a, Vec<u64>>,
|
distribution: &[u64],
|
||||||
height: usize,
|
height: usize,
|
||||||
high: u64,
|
high: u64,
|
||||||
per_second: f64,
|
per_second: f64,
|
||||||
@@ -60,7 +64,7 @@ async fn select_n<'a, R: RngCore + CryptoRng, RPC: RpcConnection>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Use a gamma distribution
|
// Use a gamma distribution
|
||||||
let mut age = GAMMA.sample(rng).exp();
|
let mut age = Gamma::<f64>::new(19.28, 1.0 / 1.61).unwrap().sample(rng).exp();
|
||||||
if age > TIP_APPLICATION {
|
if age > TIP_APPLICATION {
|
||||||
age -= TIP_APPLICATION;
|
age -= TIP_APPLICATION;
|
||||||
} else {
|
} else {
|
||||||
@@ -144,7 +148,10 @@ impl Decoys {
|
|||||||
height: usize,
|
height: usize,
|
||||||
inputs: &[SpendableOutput],
|
inputs: &[SpendableOutput],
|
||||||
) -> Result<Vec<Decoys>, RpcError> {
|
) -> Result<Vec<Decoys>, RpcError> {
|
||||||
let mut distribution = DISTRIBUTION.lock().await;
|
#[cfg(not(feature = "std"))]
|
||||||
|
let mut distribution = DISTRIBUTION().lock();
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
let mut distribution = DISTRIBUTION().lock().await;
|
||||||
|
|
||||||
let decoy_count = ring_len - 1;
|
let decoy_count = ring_len - 1;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
use core::ops::BitXor;
|
use core::ops::BitXor;
|
||||||
use std::io::{self, Read, Write};
|
use std_shims::{
|
||||||
|
vec::Vec,
|
||||||
|
io::{self, Read, Write},
|
||||||
|
};
|
||||||
|
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use core::ops::Deref;
|
use core::ops::Deref;
|
||||||
use std::collections::{HashSet, HashMap};
|
use std_shims::collections::{HashSet, HashMap};
|
||||||
|
|
||||||
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
|
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
|
||||||
|
|
||||||
@@ -28,16 +28,16 @@ pub(crate) mod decoys;
|
|||||||
pub(crate) use decoys::Decoys;
|
pub(crate) use decoys::Decoys;
|
||||||
|
|
||||||
mod send;
|
mod send;
|
||||||
pub use send::{
|
pub use send::{Fee, TransactionError, Change, SignableTransaction, Eventuality};
|
||||||
Fee, TransactionError, Change, SignableTransaction, SignableTransactionBuilder, Eventuality,
|
#[cfg(feature = "std")]
|
||||||
};
|
pub use send::SignableTransactionBuilder;
|
||||||
#[cfg(feature = "multisig")]
|
#[cfg(feature = "multisig")]
|
||||||
pub(crate) use send::InternalPayment;
|
pub(crate) use send::InternalPayment;
|
||||||
#[cfg(feature = "multisig")]
|
#[cfg(feature = "multisig")]
|
||||||
pub use send::TransactionMachine;
|
pub use send::TransactionMachine;
|
||||||
use crate::ringct::EcdhInfo;
|
use crate::ringct::EcdhInfo;
|
||||||
|
|
||||||
fn key_image_sort(x: &EdwardsPoint, y: &EdwardsPoint) -> std::cmp::Ordering {
|
fn key_image_sort(x: &EdwardsPoint, y: &EdwardsPoint) -> core::cmp::Ordering {
|
||||||
x.compress().to_bytes().cmp(&y.compress().to_bytes()).reverse()
|
x.compress().to_bytes().cmp(&y.compress().to_bytes()).reverse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
use core::ops::Deref;
|
use core::ops::Deref;
|
||||||
use std::io::{self, Read, Write};
|
use std_shims::{
|
||||||
|
vec::Vec,
|
||||||
|
io::{self, Read, Write},
|
||||||
|
};
|
||||||
|
|
||||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||||
|
|
||||||
@@ -282,6 +285,11 @@ impl<O: Clone + Zeroize> Timelocked<O> {
|
|||||||
impl Scanner {
|
impl Scanner {
|
||||||
/// Scan a transaction to discover the received outputs.
|
/// Scan a transaction to discover the received outputs.
|
||||||
pub fn scan_transaction(&mut self, tx: &Transaction) -> Timelocked<ReceivedOutput> {
|
pub fn scan_transaction(&mut self, tx: &Transaction) -> Timelocked<ReceivedOutput> {
|
||||||
|
// Only scan RCT TXs since we can only spend RCT outputs
|
||||||
|
if tx.prefix.version != 2 {
|
||||||
|
return Timelocked(tx.prefix.timelock, vec![]);
|
||||||
|
}
|
||||||
|
|
||||||
let extra = Extra::read::<&[u8]>(&mut tx.prefix.extra.as_ref());
|
let extra = Extra::read::<&[u8]>(&mut tx.prefix.extra.as_ref());
|
||||||
let extra = if let Ok(extra) = extra {
|
let extra = if let Ok(extra) = extra {
|
||||||
extra
|
extra
|
||||||
@@ -367,8 +375,8 @@ impl Scanner {
|
|||||||
let mut commitment = Commitment::zero();
|
let mut commitment = Commitment::zero();
|
||||||
|
|
||||||
// Miner transaction
|
// Miner transaction
|
||||||
if output.amount != 0 {
|
if let Some(amount) = output.amount {
|
||||||
commitment.amount = output.amount;
|
commitment.amount = amount;
|
||||||
// Regular transaction
|
// Regular transaction
|
||||||
} else {
|
} else {
|
||||||
let amount = match tx.rct_signatures.base.ecdh_info.get(o) {
|
let amount = match tx.rct_signatures.base.ecdh_info.get(o) {
|
||||||
@@ -450,10 +458,10 @@ impl Scanner {
|
|||||||
tx.prefix
|
tx.prefix
|
||||||
.outputs
|
.outputs
|
||||||
.iter()
|
.iter()
|
||||||
// Filter to miner TX outputs/0-amount outputs since we're tacking the 0-amount index
|
// Filter to v2 miner TX outputs/RCT outputs since we're tracking the RCT output index
|
||||||
// This will fail to scan blocks containing pre-RingCT miner TXs
|
|
||||||
.filter(|output| {
|
.filter(|output| {
|
||||||
matches!(tx.prefix.inputs.get(0), Some(Input::Gen(..))) || (output.amount == 0)
|
((tx.prefix.version == 2) && matches!(tx.prefix.inputs.get(0), Some(Input::Gen(..)))) ||
|
||||||
|
output.amount.is_none()
|
||||||
})
|
})
|
||||||
.count(),
|
.count(),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
use core::ops::Deref;
|
use core::ops::Deref;
|
||||||
use std::collections::HashMap;
|
use std_shims::{
|
||||||
|
sync::OnceLock,
|
||||||
use lazy_static::lazy_static;
|
vec::Vec,
|
||||||
|
string::{String, ToString},
|
||||||
|
collections::HashMap,
|
||||||
|
};
|
||||||
|
|
||||||
use zeroize::{Zeroize, Zeroizing};
|
use zeroize::{Zeroize, Zeroizing};
|
||||||
use rand_core::{RngCore, CryptoRng};
|
use rand_core::{RngCore, CryptoRng};
|
||||||
@@ -47,28 +50,32 @@ impl WordList {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
static LANGUAGES_CELL: OnceLock<HashMap<Language, WordList>> = OnceLock::new();
|
||||||
static ref LANGUAGES: HashMap<Language, WordList> = HashMap::from([
|
#[allow(non_snake_case)]
|
||||||
(Language::Chinese, WordList::new(include!("./classic/zh.rs"), 1)),
|
fn LANGUAGES() -> &'static HashMap<Language, WordList> {
|
||||||
(Language::English, WordList::new(include!("./classic/en.rs"), 3)),
|
LANGUAGES_CELL.get_or_init(|| {
|
||||||
(Language::Dutch, WordList::new(include!("./classic/nl.rs"), 4)),
|
HashMap::from([
|
||||||
(Language::French, WordList::new(include!("./classic/fr.rs"), 4)),
|
(Language::Chinese, WordList::new(include!("./classic/zh.rs"), 1)),
|
||||||
(Language::Spanish, WordList::new(include!("./classic/es.rs"), 4)),
|
(Language::English, WordList::new(include!("./classic/en.rs"), 3)),
|
||||||
(Language::German, WordList::new(include!("./classic/de.rs"), 4)),
|
(Language::Dutch, WordList::new(include!("./classic/nl.rs"), 4)),
|
||||||
(Language::Italian, WordList::new(include!("./classic/it.rs"), 4)),
|
(Language::French, WordList::new(include!("./classic/fr.rs"), 4)),
|
||||||
(Language::Portuguese, WordList::new(include!("./classic/pt.rs"), 4)),
|
(Language::Spanish, WordList::new(include!("./classic/es.rs"), 4)),
|
||||||
(Language::Japanese, WordList::new(include!("./classic/ja.rs"), 3)),
|
(Language::German, WordList::new(include!("./classic/de.rs"), 4)),
|
||||||
(Language::Russian, WordList::new(include!("./classic/ru.rs"), 4)),
|
(Language::Italian, WordList::new(include!("./classic/it.rs"), 4)),
|
||||||
(Language::Esperanto, WordList::new(include!("./classic/eo.rs"), 4)),
|
(Language::Portuguese, WordList::new(include!("./classic/pt.rs"), 4)),
|
||||||
(Language::Lojban, WordList::new(include!("./classic/jbo.rs"), 4)),
|
(Language::Japanese, WordList::new(include!("./classic/ja.rs"), 3)),
|
||||||
(Language::EnglishOld, WordList::new(include!("./classic/ang.rs"), 4)),
|
(Language::Russian, WordList::new(include!("./classic/ru.rs"), 4)),
|
||||||
]);
|
(Language::Esperanto, WordList::new(include!("./classic/eo.rs"), 4)),
|
||||||
|
(Language::Lojban, WordList::new(include!("./classic/jbo.rs"), 4)),
|
||||||
|
(Language::EnglishOld, WordList::new(include!("./classic/ang.rs"), 4)),
|
||||||
|
])
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) fn trim_by_lang(word: &str, lang: Language) -> String {
|
pub(crate) fn trim_by_lang(word: &str, lang: Language) -> String {
|
||||||
if lang != Language::EnglishOld {
|
if lang != Language::EnglishOld {
|
||||||
word.chars().take(LANGUAGES[&lang].unique_prefix_length).collect()
|
word.chars().take(LANGUAGES()[&lang].unique_prefix_length).collect()
|
||||||
} else {
|
} else {
|
||||||
word.to_string()
|
word.to_string()
|
||||||
}
|
}
|
||||||
@@ -92,7 +99,7 @@ fn key_to_seed(lang: Language, key: Zeroizing<Scalar>) -> ClassicSeed {
|
|||||||
let bytes = Zeroizing::new(key.to_bytes());
|
let bytes = Zeroizing::new(key.to_bytes());
|
||||||
|
|
||||||
// get the language words
|
// get the language words
|
||||||
let words = &LANGUAGES[&lang].word_list;
|
let words = &LANGUAGES()[&lang].word_list;
|
||||||
let list_len = u64::try_from(words.len()).unwrap();
|
let list_len = u64::try_from(words.len()).unwrap();
|
||||||
|
|
||||||
// To store the found words & add the checksum word later.
|
// To store the found words & add the checksum word later.
|
||||||
@@ -126,7 +133,7 @@ fn key_to_seed(lang: Language, key: Zeroizing<Scalar>) -> ClassicSeed {
|
|||||||
|
|
||||||
// create a checksum word for all languages except old english
|
// create a checksum word for all languages except old english
|
||||||
if lang != Language::EnglishOld {
|
if lang != Language::EnglishOld {
|
||||||
let checksum = seed[checksum_index(&seed, &LANGUAGES[&lang])].clone();
|
let checksum = seed[checksum_index(&seed, &LANGUAGES()[&lang])].clone();
|
||||||
seed.push(checksum);
|
seed.push(checksum);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +161,7 @@ pub(crate) fn seed_to_bytes(words: &str) -> Result<(Language, Zeroizing<[u8; 32]
|
|||||||
let mut matched_indices = Zeroizing::new(vec![]);
|
let mut matched_indices = Zeroizing::new(vec![]);
|
||||||
|
|
||||||
// Iterate through all the languages
|
// Iterate through all the languages
|
||||||
'language: for (lang_name, lang) in LANGUAGES.iter() {
|
'language: for (lang_name, lang) in LANGUAGES().iter() {
|
||||||
matched_indices.zeroize();
|
matched_indices.zeroize();
|
||||||
matched_indices.clear();
|
matched_indices.clear();
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
use core::fmt;
|
use core::fmt;
|
||||||
|
use std_shims::string::String;
|
||||||
|
|
||||||
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
|
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
|
||||||
use rand_core::{RngCore, CryptoRng};
|
use rand_core::{RngCore, CryptoRng};
|
||||||
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
pub(crate) mod classic;
|
pub(crate) mod classic;
|
||||||
use classic::{CLASSIC_SEED_LENGTH, CLASSIC_SEED_LENGTH_WITH_CHECKSUM, ClassicSeed};
|
use classic::{CLASSIC_SEED_LENGTH, CLASSIC_SEED_LENGTH_WITH_CHECKSUM, ClassicSeed};
|
||||||
|
|
||||||
/// Error when decoding a seed.
|
/// Error when decoding a seed.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Error)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
|
#[cfg_attr(feature = "std", derive(thiserror::Error))]
|
||||||
pub enum SeedError {
|
pub enum SeedError {
|
||||||
#[error("invalid number of words in seed")]
|
#[cfg_attr(feature = "std", error("invalid number of words in seed"))]
|
||||||
InvalidSeedLength,
|
InvalidSeedLength,
|
||||||
#[error("unknown language")]
|
#[cfg_attr(feature = "std", error("unknown language"))]
|
||||||
UnknownLanguage,
|
UnknownLanguage,
|
||||||
#[error("invalid checksum")]
|
#[cfg_attr(feature = "std", error("invalid checksum"))]
|
||||||
InvalidChecksum,
|
InvalidChecksum,
|
||||||
#[error("english old seeds don't support checksums")]
|
#[cfg_attr(feature = "std", error("english old seeds don't support checksums"))]
|
||||||
EnglishOldWithChecksum,
|
EnglishOldWithChecksum,
|
||||||
#[error("invalid seed")]
|
#[cfg_attr(feature = "std", error("invalid seed"))]
|
||||||
InvalidSeed,
|
InvalidSeed,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
use core::{ops::Deref, fmt};
|
use core::{ops::Deref, fmt};
|
||||||
use std::io;
|
use std_shims::{
|
||||||
|
vec::Vec,
|
||||||
use thiserror::Error;
|
io,
|
||||||
|
string::{String, ToString},
|
||||||
|
};
|
||||||
|
|
||||||
use rand_core::{RngCore, CryptoRng, SeedableRng};
|
use rand_core::{RngCore, CryptoRng, SeedableRng};
|
||||||
use rand_chacha::ChaCha20Rng;
|
use rand_chacha::ChaCha20Rng;
|
||||||
@@ -42,7 +44,9 @@ use crate::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
mod builder;
|
mod builder;
|
||||||
|
#[cfg(feature = "std")]
|
||||||
pub use builder::SignableTransactionBuilder;
|
pub use builder::SignableTransactionBuilder;
|
||||||
|
|
||||||
#[cfg(feature = "multisig")]
|
#[cfg(feature = "multisig")]
|
||||||
@@ -117,34 +121,35 @@ impl SendOutput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, Error)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
#[cfg_attr(feature = "std", derive(thiserror::Error))]
|
||||||
pub enum TransactionError {
|
pub enum TransactionError {
|
||||||
#[error("multiple addresses with payment IDs")]
|
#[cfg_attr(feature = "std", error("multiple addresses with payment IDs"))]
|
||||||
MultiplePaymentIds,
|
MultiplePaymentIds,
|
||||||
#[error("no inputs")]
|
#[cfg_attr(feature = "std", error("no inputs"))]
|
||||||
NoInputs,
|
NoInputs,
|
||||||
#[error("no outputs")]
|
#[cfg_attr(feature = "std", error("no outputs"))]
|
||||||
NoOutputs,
|
NoOutputs,
|
||||||
#[error("only one output and no change address")]
|
#[cfg_attr(feature = "std", error("only one output and no change address"))]
|
||||||
NoChange,
|
NoChange,
|
||||||
#[error("too many outputs")]
|
#[cfg_attr(feature = "std", error("too many outputs"))]
|
||||||
TooManyOutputs,
|
TooManyOutputs,
|
||||||
#[error("too much data")]
|
#[cfg_attr(feature = "std", error("too much data"))]
|
||||||
TooMuchData,
|
TooMuchData,
|
||||||
#[error("too many inputs/too much arbitrary data")]
|
#[cfg_attr(feature = "std", error("too many inputs/too much arbitrary data"))]
|
||||||
TooLargeTransaction,
|
TooLargeTransaction,
|
||||||
#[error("not enough funds (in {0}, out {1})")]
|
#[cfg_attr(feature = "std", error("not enough funds (in {0}, out {1})"))]
|
||||||
NotEnoughFunds(u64, u64),
|
NotEnoughFunds(u64, u64),
|
||||||
#[error("wrong spend private key")]
|
#[cfg_attr(feature = "std", error("wrong spend private key"))]
|
||||||
WrongPrivateKey,
|
WrongPrivateKey,
|
||||||
#[error("rpc error ({0})")]
|
#[cfg_attr(feature = "std", error("rpc error ({0})"))]
|
||||||
RpcError(RpcError),
|
RpcError(RpcError),
|
||||||
#[error("clsag error ({0})")]
|
#[cfg_attr(feature = "std", error("clsag error ({0})"))]
|
||||||
ClsagError(ClsagError),
|
ClsagError(ClsagError),
|
||||||
#[error("invalid transaction ({0})")]
|
#[cfg_attr(feature = "std", error("invalid transaction ({0})"))]
|
||||||
InvalidTransaction(RpcError),
|
InvalidTransaction(RpcError),
|
||||||
#[cfg(feature = "multisig")]
|
#[cfg(feature = "multisig")]
|
||||||
#[error("frost error {0}")]
|
#[cfg_attr(feature = "std", error("frost error {0}"))]
|
||||||
FrostError(FrostError),
|
FrostError(FrostError),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +185,7 @@ async fn prepare_inputs<R: RngCore + CryptoRng, RPC: RpcConnection>(
|
|||||||
));
|
));
|
||||||
|
|
||||||
tx.prefix.inputs.push(Input::ToKey {
|
tx.prefix.inputs.push(Input::ToKey {
|
||||||
amount: 0,
|
amount: None,
|
||||||
key_offsets: decoys[i].offsets.clone(),
|
key_offsets: decoys[i].offsets.clone(),
|
||||||
key_image: signable[i].1,
|
key_image: signable[i].1,
|
||||||
});
|
});
|
||||||
@@ -629,7 +634,7 @@ impl SignableTransaction {
|
|||||||
for output in &outputs {
|
for output in &outputs {
|
||||||
fee -= output.commitment.amount;
|
fee -= output.commitment.amount;
|
||||||
tx_outputs.push(Output {
|
tx_outputs.push(Output {
|
||||||
amount: 0,
|
amount: None,
|
||||||
key: output.dest.compress(),
|
key: output.dest.compress(),
|
||||||
view_tag: Some(output.view_tag).filter(|_| matches!(self.protocol, Protocol::v16)),
|
view_tag: Some(output.view_tag).filter(|_| matches!(self.protocol, Protocol::v16)),
|
||||||
});
|
});
|
||||||
@@ -688,7 +693,7 @@ impl SignableTransaction {
|
|||||||
uniqueness(
|
uniqueness(
|
||||||
&images
|
&images
|
||||||
.iter()
|
.iter()
|
||||||
.map(|image| Input::ToKey { amount: 0, key_offsets: vec![], key_image: *image })
|
.map(|image| Input::ToKey { amount: None, key_offsets: vec![], key_image: *image })
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -750,7 +755,7 @@ impl Eventuality {
|
|||||||
for (o, (expected, actual)) in outputs.iter().zip(tx.prefix.outputs.iter()).enumerate() {
|
for (o, (expected, actual)) in outputs.iter().zip(tx.prefix.outputs.iter()).enumerate() {
|
||||||
// Verify the output, commitment, and encrypted amount.
|
// Verify the output, commitment, and encrypted amount.
|
||||||
if (&Output {
|
if (&Output {
|
||||||
amount: 0,
|
amount: None,
|
||||||
key: expected.dest.compress(),
|
key: expected.dest.compress(),
|
||||||
view_tag: Some(expected.view_tag).filter(|_| matches!(self.protocol, Protocol::v16)),
|
view_tag: Some(expected.view_tag).filter(|_| matches!(self.protocol, Protocol::v16)),
|
||||||
} != actual) ||
|
} != actual) ||
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
use std::{
|
use std_shims::{
|
||||||
|
vec::Vec,
|
||||||
io::{self, Read},
|
io::{self, Read},
|
||||||
sync::{Arc, RwLock},
|
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
};
|
};
|
||||||
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
use zeroize::Zeroizing;
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
@@ -339,7 +340,7 @@ impl SignMachine<Transaction> for TransactionSignMachine {
|
|||||||
uniqueness(
|
uniqueness(
|
||||||
&sorted_images
|
&sorted_images
|
||||||
.iter()
|
.iter()
|
||||||
.map(|image| Input::ToKey { amount: 0, key_offsets: vec![], key_image: *image })
|
.map(|image| Input::ToKey { amount: None, key_offsets: vec![], key_image: *image })
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -372,7 +373,7 @@ impl SignMachine<Transaction> for TransactionSignMachine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tx.prefix.inputs.push(Input::ToKey {
|
tx.prefix.inputs.push(Input::ToKey {
|
||||||
amount: 0,
|
amount: None,
|
||||||
key_offsets: value.2.offsets.clone(),
|
key_offsets: value.2.offsets.clone(),
|
||||||
key_image: value.0,
|
key_image: value.0,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
use core::ops::Deref;
|
use core::ops::Deref;
|
||||||
use std::collections::HashSet;
|
use std_shims::{sync::OnceLock, collections::HashSet};
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
|
|
||||||
use zeroize::Zeroizing;
|
use zeroize::Zeroizing;
|
||||||
use rand_core::OsRng;
|
use rand_core::OsRng;
|
||||||
@@ -98,9 +96,7 @@ pub async fn rpc() -> Rpc<HttpRpc> {
|
|||||||
rpc
|
rpc
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
pub static SEQUENTIAL: OnceLock<Mutex<()>> = OnceLock::new();
|
||||||
pub static ref SEQUENTIAL: Mutex<()> = Mutex::new(());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! async_sequential {
|
macro_rules! async_sequential {
|
||||||
@@ -108,7 +104,7 @@ macro_rules! async_sequential {
|
|||||||
$(
|
$(
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn $name() {
|
async fn $name() {
|
||||||
let guard = runner::SEQUENTIAL.lock().await;
|
let guard = runner::SEQUENTIAL.get_or_init(|| tokio::sync::Mutex::new(())).lock().await;
|
||||||
let local = tokio::task::LocalSet::new();
|
let local = tokio::task::LocalSet::new();
|
||||||
local.run_until(async move {
|
local.run_until(async move {
|
||||||
if let Err(err) = tokio::task::spawn_local(async move { $body }).await {
|
if let Err(err) = tokio::task::spawn_local(async move { $body }).await {
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ all-features = true
|
|||||||
rustdoc-args = ["--cfg", "docsrs"]
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
hashbrown = "0.13"
|
spin = "0.9"
|
||||||
|
hashbrown = "0.14"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
std = []
|
std = []
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ mod shims {
|
|||||||
|
|
||||||
impl Read for &[u8] {
|
impl Read for &[u8] {
|
||||||
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
|
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
|
||||||
let mut read = 0;
|
let mut read = buf.len();
|
||||||
if self.len() < buf.len() {
|
if self.len() < buf.len() {
|
||||||
read = self.len();
|
read = self.len();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
|
#![doc = include_str!("../README.md")]
|
||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
@@ -6,9 +8,17 @@
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub extern crate alloc;
|
pub extern crate alloc;
|
||||||
|
|
||||||
|
pub mod sync;
|
||||||
pub mod collections;
|
pub mod collections;
|
||||||
pub mod io;
|
pub mod io;
|
||||||
|
|
||||||
|
pub mod vec {
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
pub use alloc::vec::*;
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub use std::vec::*;
|
||||||
|
}
|
||||||
|
|
||||||
pub mod str {
|
pub mod str {
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
pub use alloc::str::*;
|
pub use alloc::str::*;
|
||||||
@@ -16,9 +26,9 @@ pub mod str {
|
|||||||
pub use std::str::*;
|
pub use std::str::*;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod vec {
|
pub mod string {
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
pub use alloc::vec::*;
|
pub use alloc::string::*;
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
pub use std::vec::*;
|
pub use std::string::*;
|
||||||
}
|
}
|
||||||
|
|||||||
71
common/std-shims/src/sync.rs
Normal file
71
common/std-shims/src/sync.rs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
pub use core::sync::*;
|
||||||
|
|
||||||
|
mod mutex_shim {
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub use std::sync::*;
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
pub use spin::*;
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct ShimMutex<T>(Mutex<T>);
|
||||||
|
impl<T> ShimMutex<T> {
|
||||||
|
pub const fn new(value: T) -> Self {
|
||||||
|
Self(Mutex::new(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lock(&self) -> MutexGuard<'_, T> {
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
let res = self.0.lock().unwrap();
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
let res = self.0.lock();
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub use mutex_shim::{ShimMutex as Mutex, MutexGuard};
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub use std::sync::OnceLock;
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
mod oncelock_shim {
|
||||||
|
use super::Mutex;
|
||||||
|
|
||||||
|
pub struct OnceLock<T>(Mutex<bool>, Option<T>);
|
||||||
|
impl<T> OnceLock<T> {
|
||||||
|
pub const fn new() -> OnceLock<T> {
|
||||||
|
OnceLock(Mutex::new(false), None)
|
||||||
|
}
|
||||||
|
|
||||||
|
// These return a distinct Option in case of None so another caller using get_or_init doesn't
|
||||||
|
// transform it from None to Some
|
||||||
|
pub fn get(&self) -> Option<&T> {
|
||||||
|
if !*self.0.lock() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
self.1.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn get_mut(&mut self) -> Option<&mut T> {
|
||||||
|
if !*self.0.lock() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
self.1.as_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_or_init<F: FnOnce() -> T>(&self, f: F) -> &T {
|
||||||
|
let mut lock = self.0.lock();
|
||||||
|
if !*lock {
|
||||||
|
unsafe {
|
||||||
|
(core::ptr::addr_of!(self.1) as *mut Option<_>).write_unaligned(Some(f()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*lock = true;
|
||||||
|
drop(lock);
|
||||||
|
|
||||||
|
self.get().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
pub use oncelock_shim::*;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "coordinator"
|
name = "serai-coordinator"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Serai coordinator to prepare batches and sign transactions"
|
description = "Serai coordinator to prepare batches and sign transactions"
|
||||||
license = "AGPL-3.0-only"
|
license = "AGPL-3.0-only"
|
||||||
@@ -34,7 +34,7 @@ sp-application-crypto = { git = "https://github.com/serai-dex/substrate", defaul
|
|||||||
|
|
||||||
serai-db = { path = "../common/db" }
|
serai-db = { path = "../common/db" }
|
||||||
|
|
||||||
processor-messages = { package = "processor-messages", path = "../processor/messages" }
|
processor-messages = { package = "serai-processor-messages", path = "../processor/messages" }
|
||||||
tributary = { package = "tributary-chain", path = "./tributary" }
|
tributary = { package = "tributary-chain", path = "./tributary" }
|
||||||
|
|
||||||
serai-client = { path = "../substrate/client", features = ["serai"] }
|
serai-client = { path = "../substrate/client", features = ["serai"] }
|
||||||
|
|||||||
@@ -35,10 +35,11 @@ async fn sync_test() {
|
|||||||
|
|
||||||
// Have the rest form a P2P net
|
// Have the rest form a P2P net
|
||||||
let mut tributary_arcs = vec![];
|
let mut tributary_arcs = vec![];
|
||||||
|
let mut p2p_threads = vec![];
|
||||||
for (i, (p2p, tributary)) in tributaries.drain(..).enumerate() {
|
for (i, (p2p, tributary)) in tributaries.drain(..).enumerate() {
|
||||||
let tributary = Arc::new(RwLock::new(tributary));
|
let tributary = Arc::new(RwLock::new(tributary));
|
||||||
tributary_arcs.push(tributary.clone());
|
tributary_arcs.push(tributary.clone());
|
||||||
tokio::spawn(handle_p2p(
|
let thread = tokio::spawn(handle_p2p(
|
||||||
Ristretto::generator() * *keys[i],
|
Ristretto::generator() * *keys[i],
|
||||||
p2p,
|
p2p,
|
||||||
Arc::new(RwLock::new(HashMap::from([(
|
Arc::new(RwLock::new(HashMap::from([(
|
||||||
@@ -46,6 +47,7 @@ async fn sync_test() {
|
|||||||
ActiveTributary { spec: spec.clone(), tributary },
|
ActiveTributary { spec: spec.clone(), tributary },
|
||||||
)]))),
|
)]))),
|
||||||
));
|
));
|
||||||
|
p2p_threads.push(thread);
|
||||||
}
|
}
|
||||||
let tributaries = tributary_arcs;
|
let tributaries = tributary_arcs;
|
||||||
|
|
||||||
@@ -112,21 +114,28 @@ async fn sync_test() {
|
|||||||
assert!(syncer_tributary.read().await.tip().await != syncer_tip);
|
assert!(syncer_tributary.read().await.tip().await != syncer_tip);
|
||||||
|
|
||||||
// Verify it's now participating in consensus
|
// Verify it's now participating in consensus
|
||||||
// Because only `t` validators are used in a commit, check several commits
|
// Because only `t` validators are used in a commit, take n - t nodes offline
|
||||||
// This should be biased in favor of the syncer since we're using the syncer's view of the commit
|
// leaving only `t` nodes. Which should force it to participate in the consensus
|
||||||
for _ in 0 .. 10 {
|
// of next blocks.
|
||||||
let syncer_tributary = syncer_tributary.read().await;
|
let spares = usize::from(spec.n() - spec.t());
|
||||||
if syncer_tributary
|
for thread in p2p_threads.iter().take(spares) {
|
||||||
.reader()
|
thread.abort();
|
||||||
.parsed_commit(&syncer_tributary.tip().await)
|
|
||||||
.unwrap()
|
|
||||||
.validators
|
|
||||||
.iter()
|
|
||||||
.any(|signer| signer == &syncer_key.to_bytes())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sleep(Duration::from_secs(block_time)).await;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// wait for a block
|
||||||
|
sleep(Duration::from_secs(block_time)).await;
|
||||||
|
|
||||||
|
let syncer_tributary = syncer_tributary.read().await;
|
||||||
|
if syncer_tributary
|
||||||
|
.reader()
|
||||||
|
.parsed_commit(&syncer_tributary.tip().await)
|
||||||
|
.unwrap()
|
||||||
|
.validators
|
||||||
|
.iter()
|
||||||
|
.any(|signer| signer == &syncer_key.to_bytes())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
panic!("synced tributary didn't start participating in consensus");
|
panic!("synced tributary didn't start participating in consensus");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -458,11 +458,9 @@ impl Transaction {
|
|||||||
signed_ref.signer = Ristretto::generator() * key.deref();
|
signed_ref.signer = Ristretto::generator() * key.deref();
|
||||||
signed_ref.nonce = nonce;
|
signed_ref.nonce = nonce;
|
||||||
|
|
||||||
|
let sig_nonce = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(rng));
|
||||||
|
signed(self).signature.R = <Ristretto as Ciphersuite>::generator() * sig_nonce.deref();
|
||||||
let sig_hash = self.sig_hash(genesis);
|
let sig_hash = self.sig_hash(genesis);
|
||||||
signed(self).signature = SchnorrSignature::<Ristretto>::sign(
|
signed(self).signature = SchnorrSignature::<Ristretto>::sign(key, sig_nonce, sig_hash);
|
||||||
key,
|
|
||||||
Zeroizing::new(<Ristretto as Ciphersuite>::F::random(rng)),
|
|
||||||
sig_hash,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use core::ops::Deref;
|
||||||
use std::{io, collections::HashMap};
|
use std::{io, collections::HashMap};
|
||||||
|
|
||||||
use zeroize::Zeroizing;
|
use zeroize::Zeroizing;
|
||||||
@@ -114,11 +115,9 @@ pub fn signed_transaction<R: RngCore + CryptoRng>(
|
|||||||
let mut tx =
|
let mut tx =
|
||||||
SignedTransaction(data, Signed { signer, nonce, signature: random_signed(rng).signature });
|
SignedTransaction(data, Signed { signer, nonce, signature: random_signed(rng).signature });
|
||||||
|
|
||||||
tx.1.signature = SchnorrSignature::sign(
|
let sig_nonce = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(rng));
|
||||||
key,
|
tx.1.signature.R = Ristretto::generator() * sig_nonce.deref();
|
||||||
Zeroizing::new(<Ristretto as Ciphersuite>::F::random(rng)),
|
tx.1.signature = SchnorrSignature::sign(key, sig_nonce, tx.sig_hash(genesis));
|
||||||
tx.sig_hash(genesis),
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut nonces = HashMap::from([(signer, nonce)]);
|
let mut nonces = HashMap::from([(signer, nonce)]);
|
||||||
verify_transaction(&tx, genesis, &mut nonces).unwrap();
|
verify_transaction(&tx, genesis, &mut nonces).unwrap();
|
||||||
|
|||||||
@@ -98,10 +98,20 @@ pub trait Transaction: 'static + Send + Sync + Clone + Eq + Debug + ReadWrite {
|
|||||||
/// Obtain the challenge for this transaction's signature.
|
/// Obtain the challenge for this transaction's signature.
|
||||||
///
|
///
|
||||||
/// Do not override this unless you know what you're doing.
|
/// Do not override this unless you know what you're doing.
|
||||||
|
///
|
||||||
|
/// Panics if called on non-signed transactions.
|
||||||
fn sig_hash(&self, genesis: [u8; 32]) -> <Ristretto as Ciphersuite>::F {
|
fn sig_hash(&self, genesis: [u8; 32]) -> <Ristretto as Ciphersuite>::F {
|
||||||
<Ristretto as Ciphersuite>::F::from_bytes_mod_order_wide(
|
match self.kind() {
|
||||||
&Blake2b512::digest([genesis, self.hash()].concat()).into(),
|
TransactionKind::Signed(Signed { signature, .. }) => {
|
||||||
)
|
<Ristretto as Ciphersuite>::F::from_bytes_mod_order_wide(
|
||||||
|
&Blake2b512::digest(
|
||||||
|
[genesis.as_ref(), &self.hash(), signature.R.to_bytes().as_ref()].concat(),
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ => panic!("sig_hash called on non-signed transaction"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ ff-group-tests = { version = "0.13", path = "../ff-group-tests" }
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
alloc = ["std-shims"]
|
alloc = ["std-shims"]
|
||||||
std = ["std-shims/std"]
|
std = ["zeroize/std", "std-shims/std"]
|
||||||
|
|
||||||
dalek = ["sha2", "dalek-ff-group"]
|
dalek = ["sha2", "dalek-ff-group"]
|
||||||
ed25519 = ["dalek"]
|
ed25519 = ["dalek"]
|
||||||
|
|||||||
@@ -55,10 +55,13 @@ pub(crate) fn read_point<R: Read, G: PrimeGroup>(r: &mut R) -> std::io::Result<G
|
|||||||
let mut repr = G::Repr::default();
|
let mut repr = G::Repr::default();
|
||||||
r.read_exact(repr.as_mut())?;
|
r.read_exact(repr.as_mut())?;
|
||||||
let point = G::from_bytes(&repr);
|
let point = G::from_bytes(&repr);
|
||||||
if point.is_none().into() {
|
let Some(point) = Option::<G>::from(point) else {
|
||||||
Err(std::io::Error::new(std::io::ErrorKind::Other, "invalid point"))?;
|
Err(std::io::Error::new(std::io::ErrorKind::Other, "invalid point"))?
|
||||||
|
};
|
||||||
|
if point.to_bytes().as_ref() != repr.as_ref() {
|
||||||
|
Err(std::io::Error::new(std::io::ErrorKind::Other, "non-canonical point"))?;
|
||||||
}
|
}
|
||||||
Ok(point.unwrap())
|
Ok(point)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A pair of generators, one committing to values (primary), one blinding (alt), for an elliptic
|
/// A pair of generators, one committing to values (primary), one blinding (alt), for an elliptic
|
||||||
|
|||||||
@@ -35,5 +35,5 @@ dalek-ff-group = { path = "../dalek-ff-group", version = "0.3" }
|
|||||||
ciphersuite = { path = "../ciphersuite", version = "0.3", features = ["ed25519"] }
|
ciphersuite = { path = "../ciphersuite", version = "0.3", features = ["ed25519"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
std = ["std-shims/std", "ciphersuite/std"]
|
std = ["std-shims/std", "ciphersuite/std", "multiexp/std"]
|
||||||
default = ["std"]
|
default = ["std"]
|
||||||
|
|||||||
15
deny.toml
15
deny.toml
@@ -45,17 +45,19 @@ default = "deny"
|
|||||||
exceptions = [
|
exceptions = [
|
||||||
{ allow = ["AGPL-3.0"], name = "ethereum-serai" },
|
{ allow = ["AGPL-3.0"], name = "ethereum-serai" },
|
||||||
|
|
||||||
{ allow = ["AGPL-3.0"], name = "processor-messages" },
|
{ allow = ["AGPL-3.0"], name = "serai-message-queue" },
|
||||||
{ allow = ["AGPL-3.0"], name = "processor" },
|
|
||||||
|
{ allow = ["AGPL-3.0"], name = "serai-processor-messages" },
|
||||||
|
{ allow = ["AGPL-3.0"], name = "serai-processor" },
|
||||||
|
|
||||||
{ allow = ["AGPL-3.0"], name = "tributary-chain" },
|
{ allow = ["AGPL-3.0"], name = "tributary-chain" },
|
||||||
{ allow = ["AGPL-3.0"], name = "coordinator" },
|
{ allow = ["AGPL-3.0"], name = "serai-coordinator" },
|
||||||
|
|
||||||
{ allow = ["AGPL-3.0"], name = "tokens-pallet" },
|
{ allow = ["AGPL-3.0"], name = "serai-tokens-pallet" },
|
||||||
|
|
||||||
{ allow = ["AGPL-3.0"], name = "in-instructions-pallet" },
|
{ allow = ["AGPL-3.0"], name = "serai-in-instructions-pallet" },
|
||||||
|
|
||||||
{ allow = ["AGPL-3.0"], name = "validator-sets-pallet" },
|
{ allow = ["AGPL-3.0"], name = "serai-validator-sets-pallet" },
|
||||||
|
|
||||||
{ allow = ["AGPL-3.0"], name = "serai-runtime" },
|
{ allow = ["AGPL-3.0"], name = "serai-runtime" },
|
||||||
{ allow = ["AGPL-3.0"], name = "serai-node" },
|
{ allow = ["AGPL-3.0"], name = "serai-node" },
|
||||||
@@ -83,4 +85,5 @@ allow-registry = ["https://github.com/rust-lang/crates.io-index"]
|
|||||||
allow-git = [
|
allow-git = [
|
||||||
"https://github.com/serai-dex/substrate-bip39",
|
"https://github.com/serai-dex/substrate-bip39",
|
||||||
"https://github.com/serai-dex/substrate",
|
"https://github.com/serai-dex/substrate",
|
||||||
|
"https://github.com/monero-rs/base58-monero",
|
||||||
]
|
]
|
||||||
|
|||||||
38
message-queue/Cargo.toml
Normal file
38
message-queue/Cargo.toml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
[package]
|
||||||
|
name = "serai-message-queue"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "A message queue focused on safety"
|
||||||
|
license = "AGPL-3.0-only"
|
||||||
|
repository = "https://github.com/serai-dex/serai/tree/develop/message-log"
|
||||||
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
|
keywords = []
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# Macros
|
||||||
|
lazy_static = "1"
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
||||||
|
# Encoders
|
||||||
|
hex = "0.4"
|
||||||
|
serde_json = "1"
|
||||||
|
|
||||||
|
# Cryptography
|
||||||
|
transcript = { package = "flexible-transcript", path = "../crypto/transcript", features = ["recommended"] }
|
||||||
|
ciphersuite = { path = "../crypto/ciphersuite", features = ["ristretto"] }
|
||||||
|
schnorr-signatures = { path = "../crypto/schnorr" }
|
||||||
|
|
||||||
|
# Application
|
||||||
|
log = "0.4"
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
|
||||||
|
serai-db = { path = "../common/db" }
|
||||||
|
|
||||||
|
serai-primitives = { path = "../substrate/primitives" }
|
||||||
|
|
||||||
|
jsonrpsee = { version = "0.16", features = ["server"] }
|
||||||
15
message-queue/LICENSE
Normal file
15
message-queue/LICENSE
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
AGPL-3.0-only license
|
||||||
|
|
||||||
|
Copyright (c) 2023 Luke Parker
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License Version 3 as
|
||||||
|
published by the Free Software Foundation.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
14
message-queue/README.md
Normal file
14
message-queue/README.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Message Log
|
||||||
|
|
||||||
|
A message log for various services to communicate over.
|
||||||
|
|
||||||
|
Each message is checked to be of the claimed origin. Then, it's added to the
|
||||||
|
recipient's message queue. This queue is sequentially handled, FIFO, only
|
||||||
|
dropping messages once the recipient acknowledges it's been handled.
|
||||||
|
|
||||||
|
A client which publishes an event specifies its own ID for the publication. If
|
||||||
|
multiple publications with the same ID occur, they are assumed repeats and
|
||||||
|
dropped.
|
||||||
|
|
||||||
|
This library always panics as its error-cases should be unreachable, given its
|
||||||
|
intranet status.
|
||||||
2
message-queue/src/lib.rs
Normal file
2
message-queue/src/lib.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
mod messages;
|
||||||
|
pub use messages::*;
|
||||||
152
message-queue/src/main.rs
Normal file
152
message-queue/src/main.rs
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
use std::{
|
||||||
|
sync::{Arc, RwLock},
|
||||||
|
collections::HashMap,
|
||||||
|
};
|
||||||
|
|
||||||
|
use ciphersuite::{group::GroupEncoding, Ciphersuite, Ristretto};
|
||||||
|
use schnorr_signatures::SchnorrSignature;
|
||||||
|
|
||||||
|
use serai_primitives::NetworkId;
|
||||||
|
|
||||||
|
use jsonrpsee::{RpcModule, server::ServerBuilder};
|
||||||
|
|
||||||
|
mod messages;
|
||||||
|
use messages::*;
|
||||||
|
|
||||||
|
mod queue;
|
||||||
|
use queue::Queue;
|
||||||
|
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
static ref KEYS: Arc<RwLock<HashMap<Service, <Ristretto as Ciphersuite>::G>>> =
|
||||||
|
Arc::new(RwLock::new(HashMap::new()));
|
||||||
|
static ref QUEUES: Arc<RwLock<HashMap<Service, RwLock<Queue<serai_db::MemDb>>>>> =
|
||||||
|
Arc::new(RwLock::new(HashMap::new()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// queue RPC method
|
||||||
|
fn queue_message(meta: Metadata, msg: Vec<u8>, sig: SchnorrSignature<Ristretto>) {
|
||||||
|
{
|
||||||
|
let from = (*KEYS).read().unwrap()[&meta.from];
|
||||||
|
assert!(sig.verify(from, message_challenge(from, &msg, sig.R)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert one, and only one of these, is the coordinator
|
||||||
|
assert!(matches!(meta.from, Service::Coordinator) ^ matches!(meta.to, Service::Coordinator));
|
||||||
|
|
||||||
|
// TODO: Verify the from_id hasn't been prior seen
|
||||||
|
|
||||||
|
// Queue it
|
||||||
|
(*QUEUES).read().unwrap()[&meta.to].write().unwrap().queue_message(QueuedMessage {
|
||||||
|
from: meta.from,
|
||||||
|
msg,
|
||||||
|
sig: sig.serialize(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// get RPC method
|
||||||
|
fn get_next_message(
|
||||||
|
service: Service,
|
||||||
|
_expected: u64,
|
||||||
|
_signature: SchnorrSignature<Ristretto>,
|
||||||
|
) -> Option<QueuedMessage> {
|
||||||
|
// TODO: Verify the signature
|
||||||
|
|
||||||
|
// TODO: Verify the expected next message ID matches
|
||||||
|
|
||||||
|
let queue_outer = (*QUEUES).read().unwrap();
|
||||||
|
let queue = queue_outer[&service].read().unwrap();
|
||||||
|
let next = queue.last_acknowledged().map(|i| i + 1).unwrap_or(0);
|
||||||
|
queue.get_message(next)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ack RPC method
|
||||||
|
fn ack_message(service: Service, id: u64, _signature: SchnorrSignature<Ristretto>) {
|
||||||
|
// TODO: Verify the signature
|
||||||
|
|
||||||
|
// Is it:
|
||||||
|
// The acknowledged message should be > last acknowledged OR
|
||||||
|
// The acknowledged message should be >=
|
||||||
|
// It's the first if we save messages as acknowledged before acknowledging them
|
||||||
|
// It's the second if we acknowledge messages before saving them as acknowledged
|
||||||
|
// TODO: Check only a proper message is being acked
|
||||||
|
|
||||||
|
(*QUEUES).read().unwrap()[&service].write().unwrap().ack_message(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
// Open the DB
|
||||||
|
// TODO
|
||||||
|
let db = serai_db::MemDb::new();
|
||||||
|
|
||||||
|
let read_key = |str| {
|
||||||
|
let Ok(key) = std::env::var(str) else { None? };
|
||||||
|
|
||||||
|
let mut repr = <<Ristretto as Ciphersuite>::G as GroupEncoding>::Repr::default();
|
||||||
|
repr.as_mut().copy_from_slice(&hex::decode(key).unwrap());
|
||||||
|
Some(<Ristretto as Ciphersuite>::G::from_bytes(&repr).unwrap())
|
||||||
|
};
|
||||||
|
|
||||||
|
let register_service = |service, key| {
|
||||||
|
(*KEYS).write().unwrap().insert(service, key);
|
||||||
|
(*QUEUES).write().unwrap().insert(service, RwLock::new(Queue(db.clone(), service)));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make queues for each NetworkId, other than Serai
|
||||||
|
for network in [NetworkId::Bitcoin, NetworkId::Ethereum, NetworkId::Monero] {
|
||||||
|
// Use a match so we error if the list of NetworkIds changes
|
||||||
|
let Some(key) = read_key(match network {
|
||||||
|
NetworkId::Serai => unreachable!(),
|
||||||
|
NetworkId::Bitcoin => "BITCOIN_KEY",
|
||||||
|
NetworkId::Ethereum => "ETHEREUM_KEY",
|
||||||
|
NetworkId::Monero => "MONERO_KEY",
|
||||||
|
}) else { continue };
|
||||||
|
|
||||||
|
register_service(Service::Processor(network), key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// And the coordinator's
|
||||||
|
register_service(Service::Coordinator, read_key("COORDINATOR_KEY").unwrap());
|
||||||
|
|
||||||
|
// Start server
|
||||||
|
let builder = ServerBuilder::new();
|
||||||
|
// TODO: Set max request/response size
|
||||||
|
let listen_on: &[std::net::SocketAddr] = &["0.0.0.0".parse().unwrap()];
|
||||||
|
let server = builder.build(listen_on).await.unwrap();
|
||||||
|
|
||||||
|
let mut module = RpcModule::new(());
|
||||||
|
module
|
||||||
|
.register_method("queue", |args, _| {
|
||||||
|
let args = args.parse::<(Metadata, Vec<u8>, Vec<u8>)>().unwrap();
|
||||||
|
queue_message(
|
||||||
|
args.0,
|
||||||
|
args.1,
|
||||||
|
SchnorrSignature::<Ristretto>::read(&mut args.2.as_slice()).unwrap(),
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
module
|
||||||
|
.register_method("next", |args, _| {
|
||||||
|
let args = args.parse::<(Service, u64, Vec<u8>)>().unwrap();
|
||||||
|
get_next_message(
|
||||||
|
args.0,
|
||||||
|
args.1,
|
||||||
|
SchnorrSignature::<Ristretto>::read(&mut args.2.as_slice()).unwrap(),
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
module
|
||||||
|
.register_method("ack", |args, _| {
|
||||||
|
let args = args.parse::<(Service, u64, Vec<u8>)>().unwrap();
|
||||||
|
ack_message(
|
||||||
|
args.0,
|
||||||
|
args.1,
|
||||||
|
SchnorrSignature::<Ristretto>::read(&mut args.2.as_slice()).unwrap(),
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
server.start(module).unwrap();
|
||||||
|
}
|
||||||
40
message-queue/src/messages.rs
Normal file
40
message-queue/src/messages.rs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
use transcript::{Transcript, RecommendedTranscript};
|
||||||
|
use ciphersuite::{group::GroupEncoding, Ciphersuite, Ristretto};
|
||||||
|
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
use serai_primitives::NetworkId;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
|
||||||
|
pub enum Service {
|
||||||
|
Processor(NetworkId),
|
||||||
|
Coordinator,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct QueuedMessage {
|
||||||
|
pub from: Service,
|
||||||
|
pub msg: Vec<u8>,
|
||||||
|
pub sig: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct Metadata {
|
||||||
|
pub from: Service,
|
||||||
|
pub to: Service,
|
||||||
|
pub from_id: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn message_challenge(
|
||||||
|
from: <Ristretto as Ciphersuite>::G,
|
||||||
|
msg: &[u8],
|
||||||
|
nonce: <Ristretto as Ciphersuite>::G,
|
||||||
|
) -> <Ristretto as Ciphersuite>::F {
|
||||||
|
let mut transcript = RecommendedTranscript::new(b"Serai Message Queue v0.1");
|
||||||
|
transcript.domain_separate(b"message");
|
||||||
|
transcript.append_message(b"from", from.to_bytes());
|
||||||
|
transcript.append_message(b"msg", msg);
|
||||||
|
transcript.domain_separate(b"signature");
|
||||||
|
transcript.append_message(b"nonce", nonce.to_bytes());
|
||||||
|
<Ristretto as Ciphersuite>::hash_to_F(b"challenge", &transcript.challenge(b"challenge"))
|
||||||
|
}
|
||||||
57
message-queue/src/queue.rs
Normal file
57
message-queue/src/queue.rs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
use serai_db::{DbTxn, Db};
|
||||||
|
|
||||||
|
use crate::messages::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) struct Queue<D: Db>(pub(crate) D, pub(crate) Service);
|
||||||
|
impl<D: Db> Queue<D> {
|
||||||
|
fn key(domain: &'static [u8], key: impl AsRef<[u8]>) -> Vec<u8> {
|
||||||
|
[&[u8::try_from(domain.len()).unwrap()], domain, key.as_ref()].concat()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn message_count_key(&self) -> Vec<u8> {
|
||||||
|
Self::key(b"message_count", serde_json::to_vec(&self.1).unwrap())
|
||||||
|
}
|
||||||
|
pub(crate) fn message_count(&self) -> u64 {
|
||||||
|
self
|
||||||
|
.0
|
||||||
|
.get(self.message_count_key())
|
||||||
|
.map(|bytes| u64::from_le_bytes(bytes.try_into().unwrap()))
|
||||||
|
.unwrap_or(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn last_acknowledged_key(&self) -> Vec<u8> {
|
||||||
|
Self::key(b"last_acknowledged", serde_json::to_vec(&self.1).unwrap())
|
||||||
|
}
|
||||||
|
pub(crate) fn last_acknowledged(&self) -> Option<u64> {
|
||||||
|
self
|
||||||
|
.0
|
||||||
|
.get(self.last_acknowledged_key())
|
||||||
|
.map(|bytes| u64::from_le_bytes(bytes.try_into().unwrap()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn message_key(&self, id: u64) -> Vec<u8> {
|
||||||
|
Self::key(b"message", serde_json::to_vec(&(self.1, id)).unwrap())
|
||||||
|
}
|
||||||
|
pub(crate) fn queue_message(&mut self, msg: QueuedMessage) {
|
||||||
|
let id = self.message_count();
|
||||||
|
let msg_key = self.message_key(id);
|
||||||
|
let msg_count_key = self.message_count_key();
|
||||||
|
|
||||||
|
let mut txn = self.0.txn();
|
||||||
|
txn.put(msg_key, serde_json::to_vec(&msg).unwrap());
|
||||||
|
txn.put(msg_count_key, (id + 1).to_le_bytes());
|
||||||
|
txn.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_message(&self, id: u64) -> Option<QueuedMessage> {
|
||||||
|
self.0.get(self.message_key(id)).map(|bytes| serde_json::from_slice(&bytes).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn ack_message(&mut self, id: u64) {
|
||||||
|
let ack_key = self.last_acknowledged_key();
|
||||||
|
let mut txn = self.0.txn();
|
||||||
|
txn.put(ack_key, id.to_le_bytes());
|
||||||
|
txn.commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "processor"
|
name = "serai-processor"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Multichain processor premised on canonicity to reach distributed consensus automatically"
|
description = "Multichain processor premised on canonicity to reach distributed consensus automatically"
|
||||||
license = "AGPL-3.0-only"
|
license = "AGPL-3.0-only"
|
||||||
@@ -57,7 +57,10 @@ tokio = { version = "1", features = ["full"] }
|
|||||||
serai-db = { path = "../common/db", default-features = false }
|
serai-db = { path = "../common/db", default-features = false }
|
||||||
serai-client = { path = "../substrate/client", default-features = false }
|
serai-client = { path = "../substrate/client", default-features = false }
|
||||||
|
|
||||||
messages = { package = "processor-messages", path = "./messages" }
|
messages = { package = "serai-processor-messages", path = "./messages" }
|
||||||
|
|
||||||
|
jsonrpsee = { version = "0.16", features = ["client"] }
|
||||||
|
message-queue = { package = "serai-message-queue", path = "../message-queue" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "processor-messages"
|
name = "serai-processor-messages"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Messages sent and received by the processor"
|
description = "Messages sent and received by the processor"
|
||||||
license = "AGPL-3.0-only"
|
license = "AGPL-3.0-only"
|
||||||
@@ -22,6 +22,6 @@ bincode = "1"
|
|||||||
dkg = { path = "../../crypto/dkg", features = ["serde"] }
|
dkg = { path = "../../crypto/dkg", features = ["serde"] }
|
||||||
|
|
||||||
serai-primitives = { path = "../../substrate/primitives" }
|
serai-primitives = { path = "../../substrate/primitives" }
|
||||||
in-instructions-primitives = { path = "../../substrate/in-instructions/primitives" }
|
serai-in-instructions-primitives = { path = "../../substrate/in-instructions/primitives" }
|
||||||
tokens-primitives = { path = "../../substrate/tokens/primitives" }
|
serai-tokens-primitives = { path = "../../substrate/tokens/primitives" }
|
||||||
validator-sets-primitives = { path = "../../substrate/validator-sets/primitives" }
|
serai-validator-sets-primitives = { path = "../../substrate/validator-sets/primitives" }
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use sp_core::sr25519::Public;
|
||||||
use serai_runtime::{
|
use serai_runtime::{
|
||||||
primitives::{SeraiAddress, SubstrateAmount, Amount, Coin, Balance},
|
primitives::{SeraiAddress, SubstrateAmount, Amount, Coin, Balance},
|
||||||
assets::{AssetDetails, AssetAccount},
|
assets::{AssetDetails, AssetAccount},
|
||||||
@@ -42,14 +43,14 @@ impl Serai {
|
|||||||
) -> Result<Amount, SeraiError> {
|
) -> Result<Amount, SeraiError> {
|
||||||
Ok(Amount(
|
Ok(Amount(
|
||||||
self
|
self
|
||||||
.storage::<AssetAccount<SubstrateAmount, SubstrateAmount, ()>>(
|
.storage::<AssetAccount<SubstrateAmount, SubstrateAmount, (), Public>>(
|
||||||
"Assets",
|
"Assets",
|
||||||
"Account",
|
"Account",
|
||||||
Some(vec![scale_value(coin), scale_value(address)]),
|
Some(vec![scale_value(coin), scale_value(address)]),
|
||||||
block,
|
block,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.map(|account| account.balance)
|
.map(|account| account.balance())
|
||||||
.unwrap_or(0),
|
.unwrap_or(0),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "in-instructions-pallet"
|
name = "serai-in-instructions-pallet"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Execute calls via In Instructions from unsigned transactions"
|
description = "Execute calls via In Instructions from unsigned transactions"
|
||||||
license = "AGPL-3.0-only"
|
license = "AGPL-3.0-only"
|
||||||
@@ -24,10 +24,10 @@ frame-system = { git = "https://github.com/serai-dex/substrate", default-feature
|
|||||||
frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
|
||||||
serai-primitives = { path = "../../primitives", default-features = false }
|
serai-primitives = { path = "../../primitives", default-features = false }
|
||||||
in-instructions-primitives = { path = "../primitives", default-features = false }
|
in-instructions-primitives = { package = "serai-in-instructions-primitives", path = "../primitives", default-features = false }
|
||||||
|
|
||||||
tokens-pallet = { path = "../../tokens/pallet", default-features = false }
|
tokens-pallet = { package = "serai-tokens-pallet", path = "../../tokens/pallet", default-features = false }
|
||||||
validator-sets-pallet = { path = "../../validator-sets/pallet", default-features = false }
|
validator-sets-pallet = { package = "serai-validator-sets-pallet", path = "../../validator-sets/pallet", default-features = false }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
std = [
|
std = [
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "in-instructions-primitives"
|
name = "serai-in-instructions-primitives"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Serai instructions library, enabling encoding and decoding"
|
description = "Serai instructions library, enabling encoding and decoding"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
@@ -23,7 +23,7 @@ sp-std = { git = "https://github.com/serai-dex/substrate", default-features = fa
|
|||||||
sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
|
||||||
serai-primitives = { path = "../../primitives", default-features = false }
|
serai-primitives = { path = "../../primitives", default-features = false }
|
||||||
tokens-primitives = { path = "../../tokens/primitives", default-features = false }
|
tokens-primitives = { package = "serai-tokens-primitives", path = "../../tokens/primitives", default-features = false }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
std = [
|
std = [
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ pub fn new_partial(config: &Configuration) -> Result<PartialComponents, ServiceE
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn new_full(mut config: Configuration) -> Result<TaskManager, ServiceError> {
|
pub async fn new_full(config: Configuration) -> Result<TaskManager, ServiceError> {
|
||||||
let sc_service::PartialComponents {
|
let sc_service::PartialComponents {
|
||||||
client,
|
client,
|
||||||
backend,
|
backend,
|
||||||
@@ -156,11 +156,15 @@ pub async fn new_full(mut config: Configuration) -> Result<TaskManager, ServiceE
|
|||||||
other: (block_import, babe_link, grandpa_link, shared_voter_state, mut telemetry),
|
other: (block_import, babe_link, grandpa_link, shared_voter_state, mut telemetry),
|
||||||
} = new_partial(&config)?;
|
} = new_partial(&config)?;
|
||||||
|
|
||||||
let publish_non_global_ips = config.network.allow_non_globals_in_dht;
|
let mut net_config = sc_network::config::FullNetworkConfiguration::new(&config.network);
|
||||||
let grandpa_protocol_name =
|
let grandpa_protocol_name =
|
||||||
grandpa::protocol_standard_name(&client.block_hash(0).unwrap().unwrap(), &config.chain_spec);
|
grandpa::protocol_standard_name(&client.block_hash(0).unwrap().unwrap(), &config.chain_spec);
|
||||||
|
net_config.add_notification_protocol(sc_consensus_grandpa::grandpa_peers_set_config(
|
||||||
|
grandpa_protocol_name.clone(),
|
||||||
|
));
|
||||||
|
|
||||||
|
let publish_non_global_ips = config.network.allow_non_globals_in_dht;
|
||||||
|
|
||||||
config.network.extra_sets.push(grandpa::grandpa_peers_set_config(grandpa_protocol_name.clone()));
|
|
||||||
let warp_sync = Arc::new(grandpa::warp_proof::NetworkProvider::new(
|
let warp_sync = Arc::new(grandpa::warp_proof::NetworkProvider::new(
|
||||||
backend.clone(),
|
backend.clone(),
|
||||||
grandpa_link.shared_authority_set().clone(),
|
grandpa_link.shared_authority_set().clone(),
|
||||||
@@ -170,6 +174,7 @@ pub async fn new_full(mut config: Configuration) -> Result<TaskManager, ServiceE
|
|||||||
let (network, system_rpc_tx, tx_handler_controller, network_starter, sync_service) =
|
let (network, system_rpc_tx, tx_handler_controller, network_starter, sync_service) =
|
||||||
sc_service::build_network(sc_service::BuildNetworkParams {
|
sc_service::build_network(sc_service::BuildNetworkParams {
|
||||||
config: &config,
|
config: &config,
|
||||||
|
net_config,
|
||||||
client: client.clone(),
|
client: client.clone(),
|
||||||
transaction_pool: transaction_pool.clone(),
|
transaction_pool: transaction_pool.clone(),
|
||||||
spawn_handle: task_manager.spawn_handle(),
|
spawn_handle: task_manager.spawn_handle(),
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ frame-support = { git = "https://github.com/serai-dex/substrate", default-featur
|
|||||||
frame-executive = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
frame-executive = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
frame-benchmarking = { git = "https://github.com/serai-dex/substrate", default-features = false, optional = true }
|
frame-benchmarking = { git = "https://github.com/serai-dex/substrate", default-features = false, optional = true }
|
||||||
|
|
||||||
serai-primitives = { path = "..//primitives", default-features = false }
|
serai-primitives = { path = "../primitives", default-features = false }
|
||||||
|
|
||||||
pallet-timestamp = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
pallet-timestamp = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
|
||||||
@@ -47,10 +47,10 @@ pallet-balances = { git = "https://github.com/serai-dex/substrate", default-feat
|
|||||||
pallet-assets = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
pallet-assets = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
pallet-transaction-payment = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
pallet-transaction-payment = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
|
||||||
tokens-pallet = { path = "../tokens/pallet", default-features = false }
|
tokens-pallet = { package = "serai-tokens-pallet", path = "../tokens/pallet", default-features = false }
|
||||||
in-instructions-pallet = { path = "../in-instructions/pallet", default-features = false }
|
in-instructions-pallet = { package = "serai-in-instructions-pallet", path = "../in-instructions/pallet", default-features = false }
|
||||||
|
|
||||||
validator-sets-pallet = { path = "../validator-sets/pallet", default-features = false }
|
validator-sets-pallet = { package = "serai-validator-sets-pallet", path = "../validator-sets/pallet", default-features = false }
|
||||||
pallet-session = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
pallet-session = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
pallet-babe = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
pallet-babe = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
pallet-grandpa = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
pallet-grandpa = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tokens-pallet"
|
name = "serai-tokens-pallet"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Mint and burn Serai tokens"
|
description = "Mint and burn Serai tokens"
|
||||||
license = "AGPL-3.0-only"
|
license = "AGPL-3.0-only"
|
||||||
@@ -21,7 +21,7 @@ frame-support = { git = "https://github.com/serai-dex/substrate", default-featur
|
|||||||
pallet-assets = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
pallet-assets = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
|
||||||
serai-primitives = { path = "../../primitives", default-features = false }
|
serai-primitives = { path = "../../primitives", default-features = false }
|
||||||
tokens-primitives = { path = "../primitives", default-features = false }
|
tokens-primitives = { package = "serai-tokens-primitives", path = "../primitives", default-features = false }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
std = [
|
std = [
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tokens-primitives"
|
name = "serai-tokens-primitives"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Serai tokens primitives"
|
description = "Serai tokens primitives"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "validator-sets-pallet"
|
name = "serai-validator-sets-pallet"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Validator sets pallet"
|
description = "Validator sets pallet"
|
||||||
license = "AGPL-3.0-only"
|
license = "AGPL-3.0-only"
|
||||||
@@ -18,13 +18,14 @@ scale = { package = "parity-scale-codec", version = "3", default-features = fals
|
|||||||
scale-info = { version = "2", default-features = false, features = ["derive"] }
|
scale-info = { version = "2", default-features = false, features = ["derive"] }
|
||||||
|
|
||||||
sp-core = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
sp-core = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
sp-application-crypto = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
sp-application-crypto = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
|
||||||
frame-system = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
frame-system = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
|
||||||
serai-primitives = { path = "../../primitives", default-features = false }
|
serai-primitives = { path = "../../primitives", default-features = false }
|
||||||
validator-sets-primitives = { path = "../primitives", default-features = false }
|
validator-sets-primitives = { package = "serai-validator-sets-primitives", path = "../primitives", default-features = false }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
std = [
|
std = [
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ pub mod pallet {
|
|||||||
use scale_info::TypeInfo;
|
use scale_info::TypeInfo;
|
||||||
|
|
||||||
use sp_core::sr25519::{Public, Signature};
|
use sp_core::sr25519::{Public, Signature};
|
||||||
|
use sp_std::vec::Vec;
|
||||||
use sp_application_crypto::RuntimePublic;
|
use sp_application_crypto::RuntimePublic;
|
||||||
|
|
||||||
use frame_system::pallet_prelude::*;
|
use frame_system::pallet_prelude::*;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "validator-sets-primitives"
|
name = "serai-validator-sets-primitives"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Primitives for validator sets"
|
description = "Primitives for validator sets"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
@@ -30,4 +30,5 @@ dkg = { path = "../../crypto/dkg", default-features = false }
|
|||||||
# modular-frost = { path = "../../crypto/frost", default-features = false }
|
# modular-frost = { path = "../../crypto/frost", default-features = false }
|
||||||
# frost-schnorrkel = { path = "../../crypto/schnorrkel", default-features = false }
|
# frost-schnorrkel = { path = "../../crypto/schnorrkel", default-features = false }
|
||||||
|
|
||||||
monero-generators = { path = "../../coins/monero/generators", default-features = false, features = ["alloc"] }
|
monero-generators = { path = "../../coins/monero/generators", default-features = false }
|
||||||
|
monero-serai = { path = "../../coins/monero", default-features = false }
|
||||||
|
|||||||
Reference in New Issue
Block a user