Skip to content
Merged
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
78 changes: 76 additions & 2 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ serde_json = "1.0.149"
serde = { version = "1.0", features = ["derive"] }
reqwest = "0.11"
tokio = { version = "1", features = ["full"] }
bitcoin = "0.32.8"
16 changes: 11 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,33 @@ This repository contains a Rust implementation of the exercises and concepts fro

The code is organized by chapters, mirroring the book's progression:

- `src/ch01/` - Finite Fields
- `src/ch01_finite_fields/` - Finite Fields
- Basic field arithmetic operations
- FieldElement struct with addition, subtraction, multiplication, division, and exponentiation

- `src/ch02/` - Elliptic Curves over Finite Fields
- `src/ch02_elliptic_curves/` - Elliptic Curves over Finite Fields
- Point operations on elliptic curves
- Point addition and scalar multiplication

- `src/ch03/` - Elliptic Curves over secp256k1
- `src/ch03_ecc/` - Elliptic Curves over secp256k1
- Implementation of the secp256k1 curve used in Bitcoin
- Basically elliptic curve cryptography, as the name states
- Large number handling with BigUint/BigInt for cryptographic operations

- `src/ch04/` - Serialization
- `src/ch04_serialization/` - Serialization
- Serialization of secp256k1 points and signatures
- SEC (Standards for Efficient Cryptography) format implementation

- `src/ch05/` - Transactions
- `src/ch05_transactions/` - Transactions
- Bitcoin transaction parsing and structure
- Variable-length integer encoding/decoding
- Transaction input/output handling

- `src/cho6_script` - Script
- Bitcoin's smart contract language Script
- Implements parsing, serialization and evaluation of scripts from ScriptPubkeys and ScriptSigs
- Implements opcodes used to evaluate scripts

- `src/lib.rs` - Library entry point
- `src/main.rs` - Executable entry point
- `tests/` - Integration tests for each chapter
Expand Down
13 changes: 7 additions & 6 deletions src/ch04_serialization/ser_private_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use crate::ch04_serialization::ser_s256_field::S256Field;
use crate::ch04_serialization::{ser_s256_point, ser_signature::Signature};
use hmac::{Hmac, Mac};
use num_bigint::{BigUint, ToBigUint};
use rand::{RngCore, rngs::OsRng};
use secp256k1::constants::{CURVE_ORDER, FIELD_SIZE};
use ser_s256_point::S256Point;
use sha2::{Digest, Sha256};
Expand All @@ -12,9 +11,12 @@ type HmacSha256 = Hmac<Sha256>;
#[derive(Debug, Clone, Default)]
pub struct PrivateKey {
pub secret_bytes: S256Field,
pub point: S256Point,
pub public_key: PublicKey,
}

#[derive(Debug, Clone, Default)]
pub struct PublicKey(pub S256Point);

impl PrivateKey {
pub fn new(secret: &str) -> Self {
let bytes_secret = secret.as_bytes();
Expand All @@ -27,7 +29,7 @@ impl PrivateKey {

PrivateKey {
secret_bytes: felt,
point,
public_key: PublicKey(point),
}
}

Expand All @@ -41,15 +43,14 @@ impl PrivateKey {
pub fn sign(self, z: S256Field) -> Result<Signature, Error> {
let big_n = BigUint::from_bytes_be(&FIELD_SIZE);

let mut k_bytes = [0_u8; 32];
OsRng.fill_bytes(&mut k_bytes);

let k = Self::deterministic_k(&self, z.clone());
let r = S256Point::generate_point(k.clone().element).x.unwrap();

let k_inv = k.inv().unwrap();
let mut s = (z + r.clone() * self.secret_bytes) * k_inv;

// Normalise s to the lower half of the curve order to ensure signature
// malleability is not possible (both s and n-s are valid, low-s is canonical).
if s.element > &big_n / 2.to_biguint().unwrap() {
s = S256Field::new(big_n - s.element);
}
Expand Down
51 changes: 43 additions & 8 deletions src/ch04_serialization/ser_s256_point.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use num_bigint::{BigUint, ToBigInt, ToBigUint};
use secp256k1::constants::{FIELD_SIZE, GENERATOR_X, GENERATOR_Y};
use secp256k1::constants::{CURVE_ORDER, FIELD_SIZE, GENERATOR_X, GENERATOR_Y};
use sha2::Sha256;

use crate::ch04_serialization::ser_private_key::PrivateKey;
use crate::ch04_serialization::ser_s256_field::{S256Field, ToS256Field};
Expand Down Expand Up @@ -227,17 +228,42 @@ impl S256Point {
generator.scalar_mult(scalar)
}

// pub fn verify_sig(&self, z: S256Field, sig: Signature) -> Result<bool, Error> {
// // BUG: S256Field always uses FIELD_SIZE (the field prime p) as its modulus.
// // ECDSA requires r, s, and z to be computed modulo the curve ORDER n (CURVE_ORDER).
// // These are different constants. Until S256Field is split into a separate
// // "scalar field" type that uses CURVE_ORDER, verify_sig will produce wrong results
// // for signatures generated by sign(). See ch03 for the correct approach.
// let u = z / sig.s.clone();
// let v = sig.r.clone() / sig.s.clone();

// let generator = Self::generator();
// let total = (generator.scalar_mult(u.element) + self.scalar_mult(v.element))?;

// Ok(total.x.unwrap().element == sig.r.element)
// }

pub fn verify_sig(&self, z: S256Field, sig: Signature) -> Result<bool, Error> {
let u = z / sig.s.clone();
let v = sig.r.clone() / sig.s.clone();
// ECDSA verification: all arithmetic must be done modulo CURVE_ORDER (n), not FIELD_SIZE (p)
let n = BigUint::from_bytes_be(&CURVE_ORDER);

// Convert to modulo n arithmetic
let s_inv = sig
.s
.element
.modinv(&n)
.ok_or_else(|| Error::new(ErrorKind::InvalidInput, "s has no inverse mod n"))?;

let u = (&z.element * &s_inv) % &n;
let v = (&sig.r.element * &s_inv) % &n;

let generator = Self::generator();
let total = (generator.scalar_mult(u.element) + self.scalar_mult(v.element))?;
let total = (generator.scalar_mult(u) + self.scalar_mult(v))?;

Ok(total.x.unwrap().element == sig.r.element)
}

pub fn sec(&self, compressed: bool) -> Vec<u8> {
pub fn to_sec(&self, compressed: bool) -> Vec<u8> {
let x = self.x.as_ref().unwrap();
let y = self.y.as_ref().unwrap();

Expand Down Expand Up @@ -268,7 +294,7 @@ impl S256Point {
}
}

pub fn parse(&self, sec_bin: Vec<u8>) -> Self {
pub fn parse(sec_bin: Vec<u8>) -> Self {
// returns a Point object from a SEC binary (not hex)
let p = S256Field::from_bytes(&FIELD_SIZE);
if sec_bin[0] == 4 {
Expand Down Expand Up @@ -301,15 +327,24 @@ impl S256Point {
}
}

pub fn pubkey_from_ser(sec_bin: Vec<u8>) -> Self {
Self::parse(sec_bin)
}

// TODO: Write test cases to cover this. I forgot to do the sha256 before the Ripemd160
fn hash160(s: &[u8]) -> Vec<u8> {
let mut hasher = Ripemd160::new();
let mut hasher = Sha256::new();
hasher.update(s);
let hash1 = hasher.finalize();

let mut hasher = Ripemd160::new();
hasher.update(hash1);

hasher.finalize().to_vec()
}

fn point_hash160(&self, compressed: bool) -> Vec<u8> {
Self::hash160(&self.sec(compressed))
Self::hash160(&self.to_sec(compressed))
}

pub fn address(&self, compressed: bool, testnet: bool) -> String {
Expand Down
Loading
Loading