Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions simf/lib/ct/commitment.simf
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Confidential Transactions: commitment builders and openings.
*
* H_a = hash_to_curve(asset_id) + abf*G (asset generator)
* C = v*H_a + vbf*G (value commitment)
*
* Every assert in this module takes blinding factors, which means the value or
* asset id it checks becomes PUBLIC to anyone reading the witness.
* For checks that constrain commitments while keeping the values sealed, use
* `crate::lib::ct::relations`.
*/

use crate::lib::asserts::{assert_eq_64, assert_eq_256};
use crate::lib::u64::convert::u64_to_u256;

/// Returns true iff `p` has the same x-coordinate as `expected`, i.e. `p == +/-expected`.
/// Only use on points from the transaction, where consensus rules out the negation;
/// a `Point` from the witness could be either sign.
pub fn is_x_eq(p: Point, expected: Gej) -> bool {
let (_, x): (u1, u256) = p;
jet::gej_x_equiv(x, expected)
}

/// Asserts that `p` has the same x-coordinate as `expected`; see `is_x_eq`.
pub fn assert_x_eq(p: Point, expected: Gej) {
assert!(is_x_eq(p, expected));
}

/// Builds the asset generator `H_a = hash_to_curve(asset_id) + abf*G`.
pub fn asset_generator(asset_id: u256, abf: Scalar) -> Gej {
jet::gej_ge_add(jet::generate(abf), jet::hash_to_curve(asset_id))
}

/// Builds the value commitment `C = v*asset_gen + vbf*G`.
pub fn value_commitment(v: u64, asset_gen: Gej, vbf: Scalar) -> Gej {
jet::linear_combination_1((u64_to_u256(v), asset_gen), vbf)
}

/// Asserts that the asset generator `h` is `hash_to_curve(asset_id) + abf*G`.
pub fn assert_asset_generator(h: Point, asset_id: u256, abf: Scalar) {
assert_x_eq(h, asset_generator(asset_id, abf));
}

/// Asserts that the value commitment `c` is `v*asset_gen + vbf*G`.
pub fn assert_value_commitment(c: Point, v: u64, asset_gen: Gej, vbf: Scalar) {
assert_x_eq(c, value_commitment(v, asset_gen, vbf));
}

/// Asserts that an `(asset, amount)` pair opens to `expected_asset_id` and
/// `expected_amount`.
pub fn assert_opens_to(
asset: Asset1,
amount: Amount1,
expected_asset_id: u256,
expected_amount: u64,
abf: Scalar,
vbf: Scalar
) {
match asset {
Left(conf_asset: Point) => {
let h: Gej = asset_generator(expected_asset_id, abf);
assert_x_eq(conf_asset, h);

match amount {
Left(conf_amount: Point) => {
assert_value_commitment(conf_amount, expected_amount, h, vbf);
},
Right(explicit_amount: u64) => assert_eq_64(explicit_amount, expected_amount),
};
},
Right(explicit_asset: u256) => {
assert_eq_256(explicit_asset, expected_asset_id);

match amount {
// An explicit asset commits against the unblinded generator.
Left(conf_amount: Point) => {
let h: Gej = (jet::hash_to_curve(expected_asset_id), 1);
assert_value_commitment(conf_amount, expected_amount, h, vbf);
},
Right(explicit_amount: u64) => assert_eq_64(explicit_amount, expected_amount),
};
},
};
}
27 changes: 27 additions & 0 deletions simf/lib/ct/point.simf
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Confidential Transactions: decoding confidential points.
*
* Elements sets the bit of a CT `Point` when y is not a square, but
* `jet::decompress` reads it as "y is odd", so CT points are decoded here.
* See https://github.com/BlockstreamResearch/simplicity/issues/349
*/

use crate::lib::u1::convert::u1_to_bool;

/// Decodes a confidential asset or amount `Point`.
/// Panics if `x` is not on the curve.
pub fn conf_point_to_ge(p: Point) -> Ge {
let (not_square, x): (u1, u256) = p;
// `fe_square_root` always returns the root that is itself a square.
let y: Fe = unwrap(jet::fe_square_root(jet::fe_add(jet::fe_multiply(jet::fe_square(x), x), 7)));

match u1_to_bool(not_square) {
true => (x, jet::fe_negate(y)),
false => (x, y),
}
}

/// Decodes a confidential asset or amount `Point` into a Jacobian point with `z = 1`.
pub fn conf_point_to_gej(p: Point) -> Gej {
(conf_point_to_ge(p), 1)
}
88 changes: 88 additions & 0 deletions simf/lib/ct/proofs.simf
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Confidential Transactions: exact value and asset proofs.
*
* These checks prove what a commitment holds without revealing any blinding factor:
*
* c - v*H_0 = vbf*G (exact value, explicit asset)
* H_a - H_0 = abf*G (exact asset)
* c + H_a - (v+1)*H_0 = ((v+1)*abf + vbf)*G (exact value and asset)
*/

use crate::lib::asserts::{assert_eq_64, assert_eq_256};
use crate::lib::ct::point::{conf_point_to_ge, conf_point_to_gej};
use crate::lib::secp256k1::operations::safe_gej_normalize;
use crate::lib::u64::convert::u64_to_u256;

/// Asserts that `proof` shows knowledge of `x` with `p == x*G`.
/// Panics if `p` is the point at infinity.
fn assert_dlog(p: Gej, proof: Signature) {
let (px, _): (Fe, Fe) = safe_gej_normalize(p);
jet::bip_0340_verify((px, jet::sig_all_hash()), proof);
}

/// Asserts that `c` commits to `v` against the generator `h`.
/// The prover's secret is `x` with `c - v*h == x*G`.
pub fn assert_exact_value(c: Point, v: u64, h: Ge, proof: Signature) {
let neg_v_h: Gej = jet::scale(jet::scalar_negate(u64_to_u256(v)), (h, 1));
assert_dlog(jet::gej_ge_add(neg_v_h, conf_point_to_ge(c)), proof);
}

/// Asserts that the asset generator `h` blinds `asset_id`.
/// The prover's secret is `abf`.
pub fn assert_exact_asset(h: Point, asset_id: u256, proof: Signature) {
assert_dlog(
jet::gej_ge_add(conf_point_to_gej(h), jet::ge_negate(jet::hash_to_curve(asset_id))),
proof
);
}

/// Asserts that `c` commits to `v` of `asset_id`, where `h` is the asset
/// generator of `c`. The prover's secret is `(v+1)*abf + vbf`.
pub fn assert_exact_value_and_asset(c: Point, h: Point, asset_id: u256, v: u64, proof: Signature) {
let v_plus_1: Scalar = jet::scalar_add(u64_to_u256(v), 1);
let neg_h0: Gej = jet::scale(jet::scalar_negate(v_plus_1), (jet::hash_to_curve(asset_id), 1));
assert_dlog(
jet::gej_ge_add(jet::gej_ge_add(neg_h0, conf_point_to_ge(c)), conf_point_to_ge(h)),
proof
);
}

/// Asserts that an `(asset, amount)` pair opens to `asset_id` and `v`,
/// but without blinding factors. The prover's secret for `proof`:
///
/// - confidential asset, confidential amount: `(v+1)*abf + vbf`
/// - confidential asset, explicit amount: `abf`
/// - explicit asset, confidential amount: `vbf`
/// - explicit asset, explicit amount: unused
pub fn assert_opens_to_proof(
asset: Asset1,
amount: Amount1,
asset_id: u256,
v: u64,
proof: Signature
) {
match asset {
Left(conf_asset: Point) => {
match amount {
Left(conf_amount: Point) => {
assert_exact_value_and_asset(conf_amount, conf_asset, asset_id, v, proof)
},
Right(explicit_amount: u64) => {
assert_eq_64(explicit_amount, v);
assert_exact_asset(conf_asset, asset_id, proof);
},
};
},
Right(explicit_asset: u256) => {
assert_eq_256(explicit_asset, asset_id);

match amount {
// An explicit asset commits against the unblinded generator.
Left(conf_amount: Point) => {
assert_exact_value(conf_amount, v, jet::hash_to_curve(asset_id), proof)
},
Right(explicit_amount: u64) => assert_eq_64(explicit_amount, v),
};
},
};
}
126 changes: 126 additions & 0 deletions simf/lib/ct/relations.simf
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
* Confidential Transactions: relations between commitments.
*
* Nothing here takes a blinding factor, so nothing here discloses a committed
* value. For checks that do reveal a value or an asset id, see
* `crate::lib::ct::commitment`.
*
* A value commitment expands to
*
* C = v*H_a + vbf*G = v*H_0 + (v*abf + vbf)*G
*
* Points used in a calculation are decoded exactly; the point the result is
* compared with is checked by x-coordinate only (see `commitment::is_x_eq`).
*/

use crate::lib::ct::commitment::{is_x_eq, assert_x_eq};
use crate::lib::ct::point::{conf_point_to_ge, conf_point_to_gej};
use crate::lib::u64::convert::u64_to_u256;

/// Returns the generator that an amount of `asset` commits against:
/// the blinded generator if `asset` is confidential, else `hash_to_curve(asset_id)`.
pub fn asset_to_ge(asset: Asset1) -> Ge {
match asset {
Left(conf_asset: Point) => conf_point_to_ge(conf_asset),
Right(explicit_asset: u256) => jet::hash_to_curve(explicit_asset),
}
}

/// Returns the value commitment of an `(asset, amount)` pair.
/// An explicit amount `v` becomes the unblinded commitment `v*H_a`, as in Elements.
pub fn amount_to_gej(asset: Asset1, amount: Amount1) -> Gej {
match amount {
Left(conf_amount: Point) => conf_point_to_gej(conf_amount),
Right(explicit_amount: u64) => {
jet::scale(u64_to_u256(explicit_amount), (asset_to_ge(asset), 1))
},
}
}

/// The empty accumulator (the point at infinity).
pub fn zero() -> Gej {
jet::gej_infinity()
}

/// `acc + c`
pub fn add(acc: Gej, c: Point) -> Gej {
jet::gej_ge_add(acc, conf_point_to_ge(c))
}

/// `acc - c`
pub fn sub(acc: Gej, c: Point) -> Gej {
jet::gej_ge_add(acc, jet::ge_negate(conf_point_to_ge(c)))
}

/// `acc + amount_to_gej(asset, amount)`
pub fn add_amount(acc: Gej, asset: Asset1, amount: Amount1) -> Gej {
jet::gej_add(acc, amount_to_gej(asset, amount))
}

/// `acc - amount_to_gej(asset, amount)`
pub fn sub_amount(acc: Gej, asset: Asset1, amount: Amount1) -> Gej {
jet::gej_add(acc, jet::gej_negate(amount_to_gej(asset, amount)))
}

/// `acc + k*c`
pub fn add_scaled(acc: Gej, k: u64, c: Point) -> Gej {
jet::gej_add(acc, jet::scale(u64_to_u256(k), conf_point_to_gej(c)))
}

/// `acc - k*c`
pub fn sub_scaled(acc: Gej, k: u64, c: Point) -> Gej {
jet::gej_add(acc, jet::gej_negate(jet::scale(u64_to_u256(k), conf_point_to_gej(c))))
}

/// Returns true iff `acc == s*G`.
pub fn is_balanced(acc: Gej, s: Scalar) -> bool {
jet::gej_equiv(acc, jet::generate(s))
}

/// Asserts that `acc == s*G`.
pub fn assert_balanced(acc: Gej, s: Scalar) {
assert!(is_balanced(acc, s));
}

/// Returns true iff `q == k*p + s*G`.
pub fn is_scaled_eq(q: Point, k: u64, p: Point, s: Scalar) -> bool {
is_x_eq(q, jet::linear_combination_1((u64_to_u256(k), conf_point_to_gej(p)), s))
}

/// Asserts that `q == k*p + s*G`, i.e. that `q` holds `k` times the value of `p`.
pub fn assert_scaled_eq(q: Point, k: u64, p: Point, s: Scalar) {
assert!(is_scaled_eq(q, k, p, s));
}

/// Returns true iff `q == p + s*G`.
pub fn is_value_eq(q: Point, p: Point, s: Scalar) -> bool {
is_scaled_eq(q, 1, p, s)
}

/// Asserts that `q == p + s*G`, i.e. that two commitments hold the same value.
pub fn assert_value_eq(q: Point, p: Point, s: Scalar) {
assert_scaled_eq(q, 1, p, s);
}

/// Returns true iff `q == p + s*G` for two asset generators.
pub fn is_asset_eq(q: Point, p: Point, s: Scalar) -> bool {
is_scaled_eq(q, 1, p, s)
}

/// Asserts that `q == p + s*G`, i.e. that two asset generators blind the same
/// asset id. Here `s = abf_q - abf_p`.
pub fn assert_asset_eq(q: Point, p: Point, s: Scalar) {
assert_scaled_eq(q, 1, p, s);
}

/// Asserts that `a*x == b*y + s*G`, i.e. that the values of `x` and `y` are in
/// the ratio `b : a`.
pub fn assert_ratio_eq(a: u64, x: Point, b: u64, y: Point, s: Scalar) {
assert_balanced(sub_scaled(add_scaled(zero(), a, x), b, y), s);
}

/// Asserts that `c1 + c2 == c_sum + s*G`.
pub fn assert_sum_eq(c1: Point, c2: Point, c_sum: Point, s: Scalar) {
let sum: Gej = add(add(zero(), c1), c2);
assert_x_eq(c_sum, jet::gej_add(sum, jet::gej_negate(jet::generate(s))));
}
9 changes: 8 additions & 1 deletion simf/lib/secp256k1/operations.simf
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,16 @@ pub fn ge_to_point(p: Ge) -> Point {
}
}

/// Decompress a `Point` into affine coordinates.
/// Panics if `jet::decompress(p)` returns `None`.
pub fn point_to_ge(p: Point) -> Ge {
unwrap(jet::decompress(p))
}

/// Decompress a `Point` into a Jacobian point with `z = 1`.
/// Panics if `jet::decompress(p)` returns `None`.
pub fn point_to_gej(p: Point) -> Gej {
(unwrap(jet::decompress(p)), 1)
(point_to_ge(p), 1)
}

/// Convert the point into affine coordinates.
Expand Down
52 changes: 52 additions & 0 deletions simf/tests/ct/commitment.simf
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use crate::lib::ct::commitment::{
asset_generator,
value_commitment,
assert_x_eq,
assert_asset_generator,
assert_opens_to
};
use crate::lib::secp256k1::operations::assert_gej_ge_eq;

use crate::tests::support::dispatch::is_selected;

fn main() {
let fn_idx: u8 = witness::FUNCTION_INDEX;

// An asset generator or a value commitment, depending on the function index
let point: Point = witness::POINT;

let asset_id: u256 = witness::ASSET_ID;
let v: u64 = witness::V;
let abf: Scalar = witness::ABF;
let vbf: Scalar = witness::VBF;

let asset: Asset1 = witness::ASSET;
let amount: Amount1 = witness::AMOUNT;

let exp_ge: Ge = witness::EXPECTED_GE;

match is_selected(0, fn_idx) {
true => {
assert_gej_ge_eq(value_commitment(v, asset_generator(asset_id, abf), vbf), exp_ge);
},
false => (),
};
match is_selected(1, fn_idx) {
true => {
assert_x_eq(point, (exp_ge, 1));
},
false => (),
};
match is_selected(2, fn_idx) {
true => {
assert_asset_generator(point, asset_id, abf);
},
false => (),
};
match is_selected(3, fn_idx) {
true => {
assert_opens_to(asset, amount, asset_id, v, abf, vbf);
},
false => (),
};
}
Loading
Loading