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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
# Changelog

## [Unreleased]

- Bumped Simplex to 0.2.0.
- Aligned existing testing API with changes in Simplex.
- Confidential Transactions functions in `ct`: decoding confidential points, building and opening commitments, exact value and asset proofs, and relations between commitments.
- `point_to_ge` in `secp256k1::operations`.

## [0.2.0]

Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ The standard library implementation for [SimplicityHL](https://github.com/Blocks

```md
simf/lib
├── ct
│ ├── commitment.simf
│ │ └── Asset generators, value commitments, and openings checked with blinding factors.
│ ├── point.simf
│ │ └── Decoding of confidential asset and amount `Point`s.
│ ├── proofs.simf
│ │ └── Exact value and asset proofs that keep blinding factors secret.
│ └── relations.simf
│ └── Relations between commitments (sums, ratios, equal values and assets) that keep values hidden.
├── secp256k1
│ └── operations.simf
│ ├── Conversions between `Ge`, `Gej`, and compressed `Point`.
Expand Down
231 changes: 231 additions & 0 deletions docs/stdlib.json
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,13 @@
"output_type": "Point",
"description": "Compress an affine point to `(parity, x)`, where `parity = 1` if and only if `y` is odd."
},
{
"simplicityhl_name": "point_to_ge",
"section": "secp256k1 operations",
"input_type": "Point",
"output_type": "Ge",
"description": "Decompress a compressed `Point` into affine coordinates.\n\n## Panics\nPanics if the compressed point does not decode to a valid curve point."
},
{
"simplicityhl_name": "point_to_gej",
"section": "secp256k1 operations",
Expand Down Expand Up @@ -629,6 +636,230 @@
"output_type": "()",
"description": "Assert that a Jacobian point equals an affine point, without normalizing the Jacobian point first.\n\n## Panics\nThe assertion fails, including when either point is off-curve."
},
{
"simplicityhl_name": "conf_point_to_ge",
"section": "Confidential points",
"input_type": "Point",
"output_type": "Ge",
"description": "Decode a confidential asset or amount `Point` into affine coordinates. Elements sets the parity bit when `y` is not a square, while `jet::decompress` reads it as \"`y` is odd\", so confidential points must be decoded with this function rather than `point_to_ge` (see [simplicity#349](https://github.com/BlockstreamResearch/simplicity/issues/349)).\n\n## Panics\nPanics if `x` is not the x-coordinate of a curve point."
},
{
"simplicityhl_name": "conf_point_to_gej",
"section": "Confidential points",
"input_type": "Point",
"output_type": "Gej",
"description": "Decode a confidential asset or amount `Point` into a Jacobian point with `z = 1`; see `conf_point_to_ge`.\n\n## Panics\nPanics if `x` is not the x-coordinate of a curve point."
},
{
"simplicityhl_name": "is_x_eq",
"section": "Confidential commitments",
"input_type": "Point, Gej",
"output_type": "bool",
"description": "Check whether `p` has the same x-coordinate as `expected`, i.e. `p` equals `expected` or its negation. Only use it on points taken from the transaction, where consensus rules out the negation; a `Point` from the witness could have either sign."
},
{
"simplicityhl_name": "assert_x_eq",
"section": "Confidential commitments",
"input_type": "Point, Gej",
"output_type": "()",
"description": "Assert that `p` has the same x-coordinate as `expected`; see `is_x_eq`.\n\n## Panics\nThe assertion fails."
},
{
"simplicityhl_name": "asset_generator",
"section": "Confidential commitments",
"input_type": "u256, Scalar",
"output_type": "Gej",
"description": "Build the asset generator `H_a = hash_to_curve(asset_id) + abf*G` from an asset id and its asset blinding factor `abf`."
},
{
"simplicityhl_name": "value_commitment",
"section": "Confidential commitments",
"input_type": "u64, Gej, Scalar",
"output_type": "Gej",
"description": "Build the value commitment `C = v*asset_gen + vbf*G` from an amount `v`, an asset generator and the value blinding factor `vbf`."
},
{
"simplicityhl_name": "assert_asset_generator",
"section": "Confidential commitments",
"input_type": "Point, u256, Scalar",
"output_type": "()",
"description": "Assert that the asset generator `h` is `hash_to_curve(asset_id) + abf*G`. `h` is compared by x-coordinate only; see `is_x_eq`. Passing blinding factors makes the committed values public to anyone reading the witness.\n\n## Panics\nThe assertion fails."
},
{
"simplicityhl_name": "assert_value_commitment",
"section": "Confidential commitments",
"input_type": "Point, u64, Gej, Scalar",
"output_type": "()",
"description": "Assert that the value commitment `c` is `v*asset_gen + vbf*G`. `c` is compared by x-coordinate only; see `is_x_eq`. Passing blinding factors makes the committed values public to anyone reading the witness.\n\n## Panics\nThe assertion fails."
},
{
"simplicityhl_name": "assert_opens_to",
"section": "Confidential commitments",
"input_type": "Asset1, Amount1, u256, u64, Scalar, Scalar",
"output_type": "()",
"description": "Assert that an `(asset, amount)` pair opens to `expected_asset_id` and `expected_amount` with the blinding factors `abf` and `vbf`. Explicit parts are compared directly, and a confidential amount under an explicit asset is checked against the unblinded generator `hash_to_curve(expected_asset_id)`; blinding factors of explicit parts are ignored. Passing blinding factors makes the committed values public to anyone reading the witness.\n\n## Panics\nThe assertion fails."
},
{
"simplicityhl_name": "assert_exact_value",
"section": "Confidential proofs",
"input_type": "Point, u64, Ge, Signature",
"output_type": "()",
"description": "Assert that the value commitment `c` holds the amount `v` against the generator `h`, without revealing any blinding factor. `proof` is a BIP-340 signature over `sig_all_hash` by the secret `x` with `c - v*h = x*G`, so it is only valid for the spending transaction.\n\n## Panics\nThe assertion fails, including when the proven point is the point at infinity or a confidential point does not decode to a curve point."
},
{
"simplicityhl_name": "assert_exact_asset",
"section": "Confidential proofs",
"input_type": "Point, u256, Signature",
"output_type": "()",
"description": "Assert that the asset generator `h` blinds `asset_id`, without revealing `abf`. `proof` is a BIP-340 signature over `sig_all_hash` by the secret `abf`, since `h - hash_to_curve(asset_id) = abf*G`, so it is only valid for the spending transaction.\n\n## Panics\nThe assertion fails, including when the proven point is the point at infinity or a confidential point does not decode to a curve point."
},
{
"simplicityhl_name": "assert_exact_value_and_asset",
"section": "Confidential proofs",
"input_type": "Point, Point, u256, u64, Signature",
"output_type": "()",
"description": "Assert that the value commitment `c` holds `v` units of `asset_id`, where `h` is the asset generator of `c`, without revealing any blinding factor. `proof` is a BIP-340 signature over `sig_all_hash` by the secret `(v+1)*abf + vbf`, since `c + h - (v+1)*hash_to_curve(asset_id)` is that multiple of `G`, so it is only valid for the spending transaction.\n\n## Panics\nThe assertion fails, including when the proven point is the point at infinity or a confidential point does not decode to a curve point."
},
{
"simplicityhl_name": "assert_opens_to_proof",
"section": "Confidential proofs",
"input_type": "Asset1, Amount1, u256, u64, Signature",
"output_type": "()",
"description": "Assert that an `(asset, amount)` pair opens to `asset_id` and `v`, without revealing any blinding factor. Explicit parts are compared directly. The secret that signs `proof` depends on which parts are confidential: `(v+1)*abf + vbf` for a confidential asset and amount, `abf` for a confidential asset with an explicit amount, and `vbf` for an explicit asset with a confidential amount; `proof` is unused when both are explicit.\n\n## Panics\nThe assertion fails, including when the proven point is the point at infinity or a confidential point does not decode to a curve point."
},
{
"simplicityhl_name": "asset_to_ge",
"section": "Confidential relations",
"input_type": "Asset1",
"output_type": "Ge",
"description": "Return the generator that an amount of `asset` commits against: the blinded generator if `asset` is confidential, otherwise `hash_to_curve(asset_id)`.\n\n## Panics\nPanics if a confidential point does not decode to a curve point."
},
{
"simplicityhl_name": "amount_to_gej",
"section": "Confidential relations",
"input_type": "Asset1, Amount1",
"output_type": "Gej",
"description": "Return the value commitment of an `(asset, amount)` pair. An explicit amount `v` becomes the unblinded commitment `v*H_a`, as in Elements.\n\n## Panics\nPanics if a confidential point does not decode to a curve point."
},
{
"simplicityhl_name": "zero",
"section": "Confidential relations",
"input_type": "",
"output_type": "Gej",
"description": "Return the empty accumulator (the point at infinity) for `add`, `sub` and the other accumulating functions."
},
{
"simplicityhl_name": "add",
"section": "Confidential relations",
"input_type": "Gej, Point",
"output_type": "Gej",
"description": "Add the confidential point `c` to the accumulator: `acc + c`.\n\n## Panics\nPanics if a confidential point does not decode to a curve point."
},
{
"simplicityhl_name": "sub",
"section": "Confidential relations",
"input_type": "Gej, Point",
"output_type": "Gej",
"description": "Subtract the confidential point `c` from the accumulator: `acc - c`.\n\n## Panics\nPanics if a confidential point does not decode to a curve point."
},
{
"simplicityhl_name": "add_amount",
"section": "Confidential relations",
"input_type": "Gej, Asset1, Amount1",
"output_type": "Gej",
"description": "Add the value commitment of an `(asset, amount)` pair to the accumulator: `acc + amount_to_gej(asset, amount)`.\n\n## Panics\nPanics if a confidential point does not decode to a curve point."
},
{
"simplicityhl_name": "sub_amount",
"section": "Confidential relations",
"input_type": "Gej, Asset1, Amount1",
"output_type": "Gej",
"description": "Subtract the value commitment of an `(asset, amount)` pair from the accumulator: `acc - amount_to_gej(asset, amount)`.\n\n## Panics\nPanics if a confidential point does not decode to a curve point."
},
{
"simplicityhl_name": "add_scaled",
"section": "Confidential relations",
"input_type": "Gej, u64, Point",
"output_type": "Gej",
"description": "Add `k` times the confidential point `c` to the accumulator: `acc + k*c`.\n\n## Panics\nPanics if a confidential point does not decode to a curve point."
},
{
"simplicityhl_name": "sub_scaled",
"section": "Confidential relations",
"input_type": "Gej, u64, Point",
"output_type": "Gej",
"description": "Subtract `k` times the confidential point `c` from the accumulator: `acc - k*c`.\n\n## Panics\nPanics if a confidential point does not decode to a curve point."
},
{
"simplicityhl_name": "is_balanced",
"section": "Confidential relations",
"input_type": "Gej, Scalar",
"output_type": "bool",
"description": "Check whether `acc == s*G`, i.e. the accumulated commitments balance up to the blinding factor difference `s`."
},
{
"simplicityhl_name": "assert_balanced",
"section": "Confidential relations",
"input_type": "Gej, Scalar",
"output_type": "()",
"description": "Assert that `acc == s*G`; see `is_balanced`.\n\n## Panics\nThe assertion fails."
},
{
"simplicityhl_name": "is_scaled_eq",
"section": "Confidential relations",
"input_type": "Point, u64, Point, Scalar",
"output_type": "bool",
"description": "Check whether `q == k*p + s*G`, i.e. `q` holds `k` times the value of `p`. `q` is compared by x-coordinate only; see `is_x_eq`.\n\n## Panics\nPanics if a confidential point does not decode to a curve point."
},
{
"simplicityhl_name": "assert_scaled_eq",
"section": "Confidential relations",
"input_type": "Point, u64, Point, Scalar",
"output_type": "()",
"description": "Assert that `q == k*p + s*G`, i.e. `q` holds `k` times the value of `p`; see `is_scaled_eq`.\n\n## Panics\nThe assertion fails, including when a confidential point does not decode to a curve point."
},
{
"simplicityhl_name": "is_value_eq",
"section": "Confidential relations",
"input_type": "Point, Point, Scalar",
"output_type": "bool",
"description": "Check whether `q == p + s*G`, i.e. two value commitments hold the same value. `q` is compared by x-coordinate only; see `is_x_eq`.\n\n## Panics\nPanics if a confidential point does not decode to a curve point."
},
{
"simplicityhl_name": "assert_value_eq",
"section": "Confidential relations",
"input_type": "Point, Point, Scalar",
"output_type": "()",
"description": "Assert that `q == p + s*G`, i.e. two value commitments hold the same value; see `is_value_eq`.\n\n## Panics\nThe assertion fails, including when a confidential point does not decode to a curve point."
},
{
"simplicityhl_name": "is_asset_eq",
"section": "Confidential relations",
"input_type": "Point, Point, Scalar",
"output_type": "bool",
"description": "Check whether `q == p + s*G` for two asset generators, i.e. they blind the same asset id, with `s = abf_q - abf_p`. `q` is compared by x-coordinate only; see `is_x_eq`.\n\n## Panics\nPanics if a confidential point does not decode to a curve point."
},
{
"simplicityhl_name": "assert_asset_eq",
"section": "Confidential relations",
"input_type": "Point, Point, Scalar",
"output_type": "()",
"description": "Assert that `q == p + s*G` for two asset generators, i.e. they blind the same asset id, with `s = abf_q - abf_p`; see `is_asset_eq`.\n\n## Panics\nThe assertion fails, including when a confidential point does not decode to a curve point."
},
{
"simplicityhl_name": "assert_ratio_eq",
"section": "Confidential relations",
"input_type": "u64, Point, u64, Point, Scalar",
"output_type": "()",
"description": "Assert that `a*x == b*y + s*G`, i.e. the values committed in `x` and `y` are in the ratio `b : a`.\n\n## Panics\nThe assertion fails, including when a confidential point does not decode to a curve point."
},
{
"simplicityhl_name": "assert_sum_eq",
"section": "Confidential relations",
"input_type": "Point, Point, Point, Scalar",
"output_type": "()",
"description": "Assert that `c1 + c2 == c_sum + s*G`. `c_sum` is compared by x-coordinate only; see `is_x_eq`.\n\n## Panics\nThe assertion fails, including when a confidential point does not decode to a curve point."
},
{
"simplicityhl_name": "u1_to_u8",
"section": "`u1` conversions",
Expand Down
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)
}
Loading
Loading