-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
BUG. Defective RIST255_MULT and RIST255_MULBASE implementations. #1428
Comments
Thank you! We will check. |
@EmelyanenkoK Do you have an estimate when to expect the response? |
TVM expects scalar in non-canonical form. You can compare results with the following rust code ( use curve25519_dalek::{Scalar, RistrettoPoint};
use curve25519_dalek::ristretto::{CompressedRistretto};
fn main() {
let X: RistrettoPoint = CompressedRistretto::from_slice(hex::decode("f245308737c2a888ba56448c8cdbce9d063b57b147e063ce36c580194ef31a63").unwrap().as_slice()).unwrap().decompress().unwrap();
let n: Scalar = Scalar::from_bytes_mod_order(hex::decode("d32fcc5ae91ba05704da9df434f22fd4c2c373fdd8294bbb58bf27292aeec00a").unwrap().try_into().unwrap());
let R: RistrettoPoint = X * n;
let R2: RistrettoPoint = RistrettoPoint::mul_base(&n);
println!("{}", format!("{}", ::hex::encode(R.compress().to_bytes().as_ref())));
println!("{}", format!("{}", ::hex::encode(R2.compress().to_bytes().as_ref())));
} Seems like canonical and non-canonical forms can be calculated from each other but it requires non-trivial tinkering with higher bits. |
Hello, please don't close the issue, I had no time to work on it and come up with the answer. |
Hello! I had time recently to explore the topic, so here are the results of my research. First of all, I'm not a mathematician or Rust/C++ dev, I just happen to implement an algorithm for smart contract from the official specification that has test vectors and results of calculations of each step and I can't do it, because the values coming from the TVM's Secondly, I've tried out your code with Thirdly, it is obvious from the curve25519_dalek::scalar docs that scalar that is being passed to the MUL and MULBASE functions MUST be canonical and that also means that converting already canonical scalar to canonical format should take no effects on value: /// Check whether this `Scalar` is the canonical representative mod \\(\ell\\). This is not
/// public because any `Scalar` that is publicly observed is reduced, by scalar invariant #2.
fn is_canonical(&self) -> Choice {
self.ct_eq(&self.reduce())
} So that means that functions inside TVM should work the same way, which is not the case. Also I've tried out Looking forward for your reply. |
Let's start with that What your use curve25519_dalek::{Scalar, RistrettoPoint};
use curve25519_dalek::ristretto::{CompressedRistretto};
fn main() {
let X: RistrettoPoint = CompressedRistretto::from_slice(hex::decode("f245308737c2a888ba56448c8cdbce9d063b57b147e063ce36c580194ef31a63").unwrap().as_slice()).unwrap().decompress().unwrap();
let n: Scalar = Scalar::from_canonical_bytes(hex::decode("d32fcc5ae91ba05704da9df434f22fd4c2c373fdd8294bbb58bf27292aeec00a").unwrap().try_into().unwrap()).unwrap();
let R: RistrettoPoint = X * n;
let R2: RistrettoPoint = RistrettoPoint::mul_base(&n);
println!("{}", format!("{}", ::hex::encode(R.compress().to_bytes().as_ref())));
println!("{}", format!("{}", ::hex::encode(R2.compress().to_bytes().as_ref())));
} and "ours" use curve25519_dalek::{Scalar, RistrettoPoint};
use curve25519_dalek::ristretto::{CompressedRistretto};
fn main() {
let X: RistrettoPoint = CompressedRistretto::from_slice(hex::decode("f245308737c2a888ba56448c8cdbce9d063b57b147e063ce36c580194ef31a63").unwrap().as_slice()).unwrap().decompress().unwrap();
let n: Scalar = Scalar::from_bytes_mod_order(hex::decode("d32fcc5ae91ba05704da9df434f22fd4c2c373fdd8294bbb58bf27292aeec00a").unwrap().try_into().unwrap());
let R: RistrettoPoint = X * n;
let R2: RistrettoPoint = RistrettoPoint::mul_base(&n);
println!("{}", format!("{}", ::hex::encode(R.compress().to_bytes().as_ref())));
println!("{}", format!("{}", ::hex::encode(R2.compress().to_bytes().as_ref())));
} snippets output on your side? |
Here's output of "my" snippet:
Here's output of "your" snippet:
|
@EmelyanenkoK Here's full code that you can verify. use curve25519_dalek::{Scalar, RistrettoPoint};
use curve25519_dalek::ristretto::{CompressedRistretto};
fn main1() {
let X: RistrettoPoint = CompressedRistretto::from_slice(hex::decode("f245308737c2a888ba56448c8cdbce9d063b57b147e063ce36c580194ef31a63").unwrap().as_slice()).unwrap().decompress().unwrap();
let n: Scalar = Scalar::from_canonical_bytes(hex::decode("d32fcc5ae91ba05704da9df434f22fd4c2c373fdd8294bbb58bf27292aeec00a").unwrap().try_into().unwrap()).unwrap();
let R: RistrettoPoint = X * n;
let R2: RistrettoPoint = RistrettoPoint::mul_base(&n);
println!("{}", format!("{}", ::hex::encode(R.compress().to_bytes().as_ref())));
println!("{}", format!("{}", ::hex::encode(R2.compress().to_bytes().as_ref())));
}
fn main2() {
let X: RistrettoPoint = CompressedRistretto::from_slice(hex::decode("f245308737c2a888ba56448c8cdbce9d063b57b147e063ce36c580194ef31a63").unwrap().as_slice()).unwrap().decompress().unwrap();
let n: Scalar = Scalar::from_bytes_mod_order(hex::decode("d32fcc5ae91ba05704da9df434f22fd4c2c373fdd8294bbb58bf27292aeec00a").unwrap().try_into().unwrap());
let R: RistrettoPoint = X * n;
let R2: RistrettoPoint = RistrettoPoint::mul_base(&n);
println!("{}", format!("{}", ::hex::encode(R.compress().to_bytes().as_ref())));
println!("{}", format!("{}", ::hex::encode(R2.compress().to_bytes().as_ref())));
}
fn main() {
println!("Main 1:");
main1();
println!("\nMain 2:");
main2();
} Output:
|
@EmelyanenkoK Hey, in case you wondering I've figured out proper reducing of the scalar using
@ me if you need code with examples as proof and I will send it to you. |
Hello!
I think I've stumbled upon a bug in implementation of Ristretto scalar multiplication.
For some reason the
n
parameter (Scalar) is being preprocessed like that:ton/crypto/vm/tonops.cpp
Line 830 in eed3153
ton/crypto/vm/tonops.cpp
Line 860 in eed3153
Value of
get_ristretto256_l()
(l
onwards) is2^252 + 27742317777372353535851937790883648493
so that means that if the Scalar passed is less thanl
it goes directly inn
(n == scalar_passed
) and thus we get correct results, but if Scalar is equal or greater thanl
we end up with some small value inn
and thus get wrong final results.Here's a real-life example of what we should get (tested via
curve25519_dalek
, code below) and what we get in reality from TVM.Let's take some point on a curve
X
and Scalarn
that is greater thanl
where:The results we should get are (
B
beingRISTRETTO_BASEPOINT
):But when processed through TVM we get different results:
So as we saw the resulting numbers are not correct.
Please fix this if it's indeed a bug cause it may pose real threat for some applications' security and working capacity.
Additionally, I attach a snippet of code for Rust script to quickly plug it in and test this issue yourself.
Looking forward for your reply.
Thanks!
The text was updated successfully, but these errors were encountered: