-
Notifications
You must be signed in to change notification settings - Fork 1
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
feat: ecdsa on BabyJubJub #17
Closed
Closed
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
2f0e317
feat(ecdsa): git commit -m "feat: skeleton code for ECDSA" --no-verify
f6a4e4f
refactor(ecdsa): add: calculate_signature ECDSA
2560b7d
refactor(ecdsa): refactor: reduce_field
f1e8b13
style(ecdsa): refactor: mod_inv
781a18a
feat(ecdsa): bug: mod_inv, calculating_signature complete, focus on t…
b0eb5a2
feat(ecdh): sd
951b2f3
fix(ecdsa): feat: ECDSA => Integration Tests
727a355
feat(ecdsa): git commit -m "feat: ECDSA implementation" --no-verify
263a6e3
refactor(ecdsa): yarn fmt -- 4 files
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
[workspace] | ||
members = ["packages/ecdh", "packages/merkle-trees"] | ||
members = ["packages/ecdh", "packages/ecdsa", "packages/merkle-trees"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
[package] | ||
name = "ecdsa" | ||
type = "bin" | ||
authors = ["YashBit"] | ||
compiler_version = ">=0.30.0" |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
// Globals Edward Curves supported Baby JubJub | ||
use dep::std::ec::consts::te::{baby_jubjub}; | ||
|
||
global BJJ = baby_jubjub(); | ||
global G = BJJ.base8; | ||
global BJJ_ORDER = 2736030358979909402780800718157159386076813972158567259200215660948447373041; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
mod globals; | ||
use dep::std; | ||
use dep::std::ec::tecurve::affine::{Curve, Point}; | ||
|
||
// @@@@@@ Core ECDSA Implementation | ||
|
||
/// Calculates an ECDSA signature for a given message using a private key and a random nonce. | ||
/// # Arguments | ||
/// * `message` - A 32-byte array representing the message to be signed. | ||
/// * `random_nonce` - A `Field` element representing the random nonce used in the signature generation. | ||
/// * `private_key` - A `Field` element representing the signer's private key. | ||
/// # Returns | ||
/// A tuple containing two `u64` elements (r, s) that represent the ECDSA signature. | ||
pub fn calculate_signature( | ||
message: [u8; 32], | ||
random_nonce: Field, | ||
private_key: Field, | ||
) -> (u64, u64) { | ||
let z = field_from_bytes(std::hash::sha256(message), true); | ||
// Noir cannot generate a random number, needs to be inputted from Integration Tests | ||
// Random Point | ||
let point = globals::BJJ.curve.mul(random_nonce, globals::G); | ||
let r = (point.x as u64) % (globals::BJJ_ORDER as u64); | ||
let s = ((mod_inv(random_nonce, globals::BJJ_ORDER) as u64) | ||
* ((z as u64) + r * (private_key as u64))) | ||
% (globals::BJJ_ORDER as u64); | ||
if (r == 0) | (s == 0) { | ||
assert(false); | ||
} | ||
(r, s) | ||
} | ||
|
||
/// Verifies an ECDSA signature against a given message hash. | ||
/// # Arguments | ||
/// * `signature` - A tuple containing two `Field` elements (r, s) representing the signature. | ||
/// * `message` - A 32-byte array representing the message that was signed. | ||
/// * `public_key` - A `Point` representing the signer's public key. | ||
/// # Returns | ||
/// A boolean indicating whether the signature is valid (`true`) or not (`false`). | ||
pub fn verify_signature(signature: (u64, u64), message: [u8; 32], public_key: Point) -> bool { | ||
let (r, s) = signature; | ||
let z = field_from_bytes(std::hash::sha256(message), true); | ||
|
||
let signature_valid = (r >= 1) | ||
& (r <= (globals::BJJ_ORDER as u64) - 1) | ||
& (s >= 1) | ||
& (s <= (globals::BJJ_ORDER as u64) - 1); | ||
|
||
let w = mod_inv(s as Field, globals::BJJ_ORDER); | ||
|
||
// Calculate first point scalar | ||
let z_u64 = z as u64; | ||
let w_u64 = w as u64; | ||
let scalar1_temp = z_u64 * w_u64; | ||
let scalar1 = scalar1_temp % (globals::BJJ_ORDER as u64); | ||
|
||
// Calculate second point scalar | ||
let scalar2_temp = r * w_u64; | ||
let scalar2 = scalar2_temp % (globals::BJJ_ORDER as u64); | ||
|
||
let point1 = globals::BJJ.curve.mul(scalar1 as Field, globals::G); | ||
let point2 = globals::BJJ.curve.mul(scalar2 as Field, public_key); | ||
let sum_point: Point = globals::BJJ.curve.add(point1, point2); | ||
|
||
let sum_point_x_u64 = sum_point.x as u64; | ||
let x_mod = sum_point_x_u64 % (globals::BJJ_ORDER as u64); | ||
let x_valid = x_mod == r; | ||
|
||
signature_valid & x_valid | ||
} | ||
|
||
/// Computes the modular inverse of a field element with respect to a given modulus. | ||
/// # Arguments | ||
/// * `a` - A `Field` element for which the modular inverse is to be calculated. | ||
/// * `m` - A `Field` element representing the modulus. | ||
/// # Returns | ||
/// The modular inverse of `a` modulo `m`. | ||
pub fn mod_inv(a: Field, m: Field) -> Field { | ||
let result = if m == 1 { | ||
1 | ||
} else { | ||
let mut a = a; | ||
let mut m0: Field = m; | ||
let mut y: Field = 0; | ||
let mut x: Field = 1; | ||
let mut inverse_found: Field = 0; | ||
|
||
// We need to set a reasonable upper bound for the number of iterations | ||
// This should be log2(m) + 1, but we'll use a constant for simplicity | ||
// Adjust this value based on the maximum expected size of your modulus | ||
let max_iterations = 256; | ||
|
||
for _ in 0..max_iterations { | ||
if (a as u32 > 1) & (inverse_found as u32 == 0) { | ||
// q is quotient | ||
let q: Field = a / m0; | ||
let mut t: Field = m0; | ||
|
||
// m0 is remainder now, process same as Euclid's algo | ||
m0 = (a as u32 % m0 as u32) as Field; | ||
a = t; | ||
t = y; | ||
|
||
// Update x and y | ||
y = x - q * y; | ||
x = t; | ||
} else { | ||
inverse_found = 1; | ||
} | ||
} | ||
|
||
// Make x positive | ||
if (x as i32) < 0 { | ||
x += m; | ||
} | ||
|
||
// Check if a and m are coprime | ||
if a != 1 { | ||
assert(false); // or use Noir's specific assertion/error handling | ||
} | ||
|
||
x | ||
}; | ||
|
||
result | ||
} | ||
|
||
/// Converts a byte array to a field element. | ||
/// # Arguments | ||
/// * `bytes` - A fixed-size array of 32 bytes. | ||
/// * `big_endian` - A boolean indicating if the byte array is in big-endian format. | ||
/// # Returns | ||
/// A `Field` element representing the converted byte array. | ||
pub fn field_from_bytes(bytes: [u8; 32], big_endian: bool) -> Field { | ||
let mut as_field: Field = 0; | ||
let mut offset: Field = 1; | ||
|
||
for i in 0..32 { | ||
let index = if big_endian { 31 - i } else { i }; | ||
as_field += (bytes[index] as Field) * offset; | ||
offset *= 256; | ||
} | ||
|
||
as_field | ||
} | ||
|
||
/// Computes a public key from a private key using the Baby JubJub curve. | ||
/// # Arguments | ||
/// * `private_key` - The private key as a `Field` element. | ||
/// # Returns | ||
/// The corresponding `Point` on the Baby JubJub curve. | ||
pub fn derive_public_key(private_key: Field) -> Point { | ||
let base_point = Point::new( | ||
5299619240641551281634865583518297030282874472190772894086521144482721001553, | ||
16950150798460657717958625567821834550301663161624707787222815936182638968203, | ||
); | ||
let baby_jubjub_curve = Curve::new(168700, 168696, base_point); | ||
baby_jubjub_curve.mul(private_key, base_point) | ||
} | ||
|
||
/// Optimized public key derivation using Baby JubJub curve. | ||
/// # Arguments | ||
/// * `private_key` - The private key as a `Field` element. | ||
/// # Returns | ||
/// The public key as a `Point` on the Baby JubJub curve. | ||
pub fn derive_public_key_optimized(private_key: Field) -> Point { | ||
globals::BJJ.curve.mul(private_key, globals::G) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
mod lib; | ||
|
||
fn main(private_key: [u8; 32], message: [u8; 32], random_nonce: [u8; 32]) { | ||
let private_key_field = lib::field_from_bytes(private_key, true); | ||
let random_nonce_field = lib::field_from_bytes(random_nonce, true); | ||
let public_key = lib::derive_public_key_optimized(private_key_field); | ||
let signature = lib::calculate_signature(message, random_nonce_field, private_key_field); | ||
// let is_valid = lib::verify_signature(signature, message, public_key); | ||
// assert(is_valid == true); | ||
let u: bool = false; | ||
assert(u != true); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import { BarretenbergBackend } from '@noir-lang/backend_barretenberg' | ||
import { Noir } from '@noir-lang/noir_js' | ||
import { ProofData } from '@noir-lang/types' | ||
import { expect } from 'chai' | ||
import { randomBytes } from 'crypto' | ||
import { readFileSync } from 'fs' | ||
import { resolve } from 'path' | ||
import 'mocha' | ||
|
||
const BJJ_ORDER = BigInt('2736030358979909402780800718157159386076813972158567259200215660948447373041') | ||
|
||
function generatePrivateKey(): Uint8Array { | ||
return randomBytes(32) | ||
} | ||
|
||
function generateMessage(): Uint8Array { | ||
return randomBytes(32) | ||
} | ||
|
||
function generateRandomNonce(): Uint8Array { | ||
const randomBytes = new Uint8Array(32) | ||
crypto.getRandomValues(randomBytes) | ||
let nonceBigInt = BigInt( | ||
'0x' + Array.from(randomBytes) | ||
.map(b => b.toString(16).padStart(2, '0')) | ||
.join(''), | ||
) | ||
nonceBigInt = nonceBigInt % BJJ_ORDER | ||
const resultArray = new Uint8Array(32) | ||
for (let i = 0; i < 32; i++) { | ||
resultArray[31 - i] = Number(nonceBigInt & BigInt(255)) | ||
nonceBigInt = nonceBigInt >> BigInt(8) | ||
} | ||
return resultArray | ||
} | ||
|
||
describe('ECDSA Circuit Tests', function() { | ||
let noir: Noir | ||
let backend: BarretenbergBackend | ||
let correctProof: ProofData | ||
|
||
beforeEach(async () => { | ||
const circuitFile = readFileSync(resolve(__dirname, '../../../target/ecdsa.json'), 'utf-8') | ||
const circuit = JSON.parse(circuitFile) | ||
backend = new BarretenbergBackend(circuit) | ||
noir = new Noir(circuit, backend) | ||
|
||
const privateKey = generatePrivateKey() | ||
const message = generateMessage() | ||
const randomNonce = generateMessage() | ||
|
||
console.log(privateKey) | ||
console.log(message) | ||
console.log(randomNonce) | ||
|
||
// Convert Uint8Array to regular arrays | ||
const input = { | ||
private_key: Array.from(privateKey), | ||
message: Array.from(message), | ||
random_nonce: Array.from(randomNonce), | ||
} | ||
|
||
correctProof = await noir.generateProof(input) | ||
}) | ||
|
||
it('Should generate valid proof for correct input', async function() { | ||
expect(correctProof.proof).to.be.instanceOf(Uint8Array) | ||
}) | ||
|
||
it('Should verify valid proof for correct input', async function() { | ||
expect(correctProof).to.not.be.undefined // Ensure proof is generated | ||
const verification = await noir.verifyProof(correctProof) | ||
expect(verification).to.be.true | ||
}) | ||
}) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we have a test and a proper README with instructions on how to use, import, test, and with a benchmark? Seems like this library would benefit from using noir-edwards too.
A lot of this code is AI-generated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On it.
Yes, AI Generated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optimised version of BabyJubJub already implements noir-edwards I believe.