Skip to content
Closed
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
6 changes: 3 additions & 3 deletions .github/workflows/build-wasm.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
name: Build Wasm

"on":
push:
branches:
- master
# push:
# branches:
# - master
pull_request:
branches:
- master
Expand Down
40 changes: 40 additions & 0 deletions .github/workflows/publish-wasm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Publish wasm package

on:
push:
tags:
- 'v*' # Capture pushes to tags that start with 'v', e.g., v0.1.0

jobs:
build-and-publish:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown

- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'

- name: Build wasm package
run: |
cd bbs_wasm
wasm-pack build --target web

- name: Publish to npm
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
cd bbs_wasm
wasm-pack publish --access public
10 changes: 7 additions & 3 deletions .github/workflows/rust-test.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
name: Rust CI

"on":
push:
branches:
- master
# push:
# branches:
# - master
pull_request:
branches:
- master
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
cancel-in-progress: true

jobs:
build-and-test:
name: Build and test signer
Expand Down
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,7 @@ temp
.vscode

# rust
**/target
**/target

# mac
**/.DS_Store
19 changes: 1 addition & 18 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ use bbs::bbs_bn254::*;

> run `cargo bench -p bbs` to run the benchmarks.

## Npm Package

> npm i bbs_wasm

## Reference

+ [Revisiting BBS Signatures](https://eprint.iacr.org/2023/275)

## Thanks

+ [arkworks::algebra](https://github.com/arkworks-rs/algebra)
11 changes: 10 additions & 1 deletion bbs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ substrate-bn = "0.6.0"
sha3 = "0.10"
serde_json = "1.0.149"
serde = { version = "1.0.228", features = ["derive"] }
hex = "0.4.3"

[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }
criterion = { version = "0.5", features = ["html_reports"] }

[[bin]]
name = "tools"
path = "script/tools.rs"

[[bin]]
name = "hash_test"
path = "script/hash_test.rs"
38 changes: 38 additions & 0 deletions bbs/script/hash_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use std::str::FromStr;
use ark_bn254::Fr as Scalar;
use ark_ff::BigInteger;
use ark_ff::PrimeField;
use sha3::{Digest, Keccak256};

fn main() {
let ctx = "LetsVerify".as_bytes();
let m0 = Scalar::from_str("48305238028827234457057068308976830949311451167141786071899284143701496669988").unwrap();
let m1 = Scalar::from_str("30362564611297414121344052595118041020684015714278959822893592029797294529055").unwrap();
let m2 = Scalar::from_str("89017634352366059964332753369766654038457760534865200995547152036320389318152").unwrap();
let m3 = Scalar::from_str("89017634352366059964332753369766654038457760534865200995547152036320389318152").unwrap();

let mut hasher = Keccak256::new();
let mut payload = Vec::new();

let mut ctx_bytes = [0u8; 32];
let len = ctx.len().min(32);
ctx_bytes[..len].copy_from_slice(&ctx[..len]);
payload.extend_from_slice(&ctx_bytes);

for m in &[m0, m1, m2, m3] {
let m_bytes = m.into_bigint().to_bytes_be();
let mut padded = [0u8; 32];
let offset = 32 - m_bytes.len().min(32);
padded[offset..].copy_from_slice(&m_bytes);
payload.extend_from_slice(&padded);
}

hasher.update(&payload);
let hash_result = hasher.finalize();
println!("Payload Hex: {}", hex::encode(&payload));
println!("Hash: {}", hex::encode(&hash_result));

let c = Scalar::from_be_bytes_mod_order(&hash_result);
// Print decimal representation
println!("C: {}", c);
}
21 changes: 21 additions & 0 deletions bbs/script/tools.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use sha3::{Digest, Keccak256};

pub fn main() {
let mut hasher = Keccak256::new();
let m1 = String::from("Age=18");
let m2 = String::from("Nationality=US");
let m3 = String::from("Verified=true");
let m4 = String::from("Empty");

hasher.update(m1.as_bytes());
println!("Hash of Age=18: 0x{}", hex::encode(hasher.finalize_reset()));

hasher.update(m2.as_bytes());
println!("Hash of Nationality=US: 0x{}", hex::encode(hasher.finalize_reset()));

hasher.update(m3.as_bytes());
println!("Hash of Verified=true: 0x{}", hex::encode(hasher.finalize_reset()));

hasher.update(m4.as_bytes());
println!("Hash of Empty: 0x{}", hex::encode(hasher.finalize_reset()));
}
2 changes: 1 addition & 1 deletion bbs/src/bbs.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Key gen related functions here.
//! Basic BBS scheme functions here.
#![allow(non_snake_case)]

use ark_bn254::Bn254;
Expand Down
101 changes: 101 additions & 0 deletions bbs/src/extend.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
//! Extend funcs and methods for supporting our scheme.
#![allow(non_snake_case)]

use crate::pub_use::*;
use ark_std::{UniformRand, rand::RngCore};

use crate::extend_structs::{PartialSignature, UserCommitment};
use crate::structs::{Signature};

pub struct BBSPlusExtendedScheme;

use ark_ff::{BigInteger, PrimeField};
use ark_bn254::{Fr as Scalar, G1Affine as G1, G2Affine as G2};

/// Converts a Field element to BIG ENDIAN bytes (32 bytes for Fr/Fq)
pub fn field_to_bytes_be<F: PrimeField>(f: &F) -> Vec<u8> {
f.into_bigint().to_bytes_be()
}

/// Serializes G1 into 64 bytes (X || Y) for EVM compatibility
pub fn push_g1_to_bytes(p: &G1, buf: &mut Vec<u8>) {
buf.extend_from_slice(&field_to_bytes_be(&p.x));
buf.extend_from_slice(&field_to_bytes_be(&p.y));
}

/// Serializes G2 into 128 bytes (X.c1 || X.c0 || Y.c1 || Y.c0) for EVM Bn254 compatibility
pub fn push_g2_to_bytes(p: &G2, buf: &mut Vec<u8>) {
buf.extend_from_slice(&field_to_bytes_be(&p.x.c1));
buf.extend_from_slice(&field_to_bytes_be(&p.x.c0));
buf.extend_from_slice(&field_to_bytes_be(&p.y.c1));
buf.extend_from_slice(&field_to_bytes_be(&p.y.c0));
}

impl BBSPlusExtendedScheme {
/// Step 2: User computes blinded commitment C2
pub fn user_commit(
m_null: &Scalar,
m_gamma: &Scalar,
lambda: &Scalar,
h_bases: &[G1],
) -> UserCommitment {
let l = h_bases.len();
let h_null = &h_bases[l - 2];
let h_gamma = &h_bases[l - 1];

let mut c2_proj = *h_null * *m_null;
c2_proj += *h_gamma * *m_gamma;
c2_proj = c2_proj * *lambda;
UserCommitment {
C2: c2_proj.into_affine(),
}
}

/// Step 3: Signer generates partial signature
pub fn signer_sign<R: RngCore>(
rng: &mut R,
sk: &Scalar,
messages: &[Scalar], // public messages m_0 to m_{l-3}
h_bases: &[G1], // H0 to H_{l-1}
g1: &G1,
c2: &G1,
) -> PartialSignature {
let l = h_bases.len();
assert_eq!(messages.len(), l - 2, "Public messages count must be l-2");

// C1 = G1 + \sum m_i H_i
let mut c1_proj = g1.into_group();
for (m, h) in messages.iter().zip(h_bases.iter()) {
c1_proj += *h * *m;
}

let e = Scalar::rand(rng);
let mut denominator = *sk;
denominator += e;
let inv_denominator = denominator.inverse().unwrap();

let a1 = (c1_proj * inv_denominator).into_affine();
let a2_prime = (*c2 * inv_denominator).into_affine();

PartialSignature {
A1: a1,
A2_prime: a2_prime,
e,
}
}

/// Step 4.1: User unblinds the partial signature to get the full signature (A, e)
pub fn user_unblind(
partial_sig: &PartialSignature,
lambda: &Scalar,
) -> Signature {
let inv_lambda = lambda.inverse().unwrap();
let a2 = (partial_sig.A2_prime * inv_lambda).into_affine();
let a = (partial_sig.A1.into_group() + a2).into_affine();

Signature {
A: a,
e: partial_sig.e,
}
}
}
58 changes: 58 additions & 0 deletions bbs/src/extend_structs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//! Extend structs for our scheme
#![allow(non_snake_case)]

use ark_bn254::{Fr as Scalar, G1Affine as G1};
use serde::{Deserialize, Serialize};

use crate::modified_serde::*;

/// User's blinded commitment for partial signature
/// C2 = lambda * (m_null * H_null + m_gamma * H_gamma)
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct UserCommitment {
#[serde(serialize_with = "serialize_g1", deserialize_with = "deserialize_g1")]
pub C2: G1,
}

/// Partial signature generated by the Signer
/// Contains two A points and the random scalar e
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PartialSignature {
#[serde(serialize_with = "serialize_g1", deserialize_with = "deserialize_g1")]
pub A1: G1,
#[serde(serialize_with = "serialize_g1", deserialize_with = "deserialize_g1")]
pub A2_prime: G1,
#[serde(
serialize_with = "serialize_scalar",
deserialize_with = "deserialize_scalar"
)]
pub e: Scalar,
}

/// NIZK Proof for BBS+ partial disclosure
/// pi = (A_bar, B_bar, U, s, t, hidden_responses)
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct BBSPlusNIZKProof {
#[serde(serialize_with = "serialize_g1", deserialize_with = "deserialize_g1")]
pub A_bar: G1,
#[serde(serialize_with = "serialize_g1", deserialize_with = "deserialize_g1")]
pub B_bar: G1,
#[serde(serialize_with = "serialize_g1", deserialize_with = "deserialize_g1")]
pub U: G1,
#[serde(
serialize_with = "serialize_scalar",
deserialize_with = "deserialize_scalar"
)]
pub s: Scalar,
#[serde(
serialize_with = "serialize_scalar",
deserialize_with = "deserialize_scalar"
)]
pub t: Scalar,
/// Store the responses for hidden values (e.g., u_gamma)
#[serde(
serialize_with = "serialize_vec_scalar",
deserialize_with = "deserialize_vec_scalar"
)]
pub hidden_responses: Vec<Scalar>,
}
4 changes: 3 additions & 1 deletion bbs/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
pub mod bbs;
mod modified_serde;
pub mod pok;
mod pub_use;
pub mod pub_use;
pub mod structs;
pub mod extend;
pub mod extend_structs;

pub fn add(left: u64, right: u64) -> u64 {
left + right
Expand Down
Loading
Loading