feat: add simple contract
This commit is contained in:
77
__crypto/simple_contract/src/credit.rs
Normal file
77
__crypto/simple_contract/src/credit.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
use std::{collections::HashMap, fs, fs::File};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use crate::{tx::Transaction, util::{SimpleError, XResult}};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct CreditContract {
|
||||
pub credit_limit: u32,
|
||||
pub issue_amount: u32,
|
||||
pub admin: String,
|
||||
pub credit: HashMap<String, u32>,
|
||||
}
|
||||
|
||||
impl CreditContract {
|
||||
pub fn new(tx: &Transaction, name: &str, credit_limit: u32) -> XResult<Self> {
|
||||
let c = Self {
|
||||
credit_limit,
|
||||
issue_amount: 0,
|
||||
admin: tx.sender.clone().unwrap(),
|
||||
credit: HashMap::new(),
|
||||
};
|
||||
Self::save(name, &c)?;
|
||||
Ok(c)
|
||||
}
|
||||
|
||||
pub fn issue(&mut self, tx: &Transaction, receiver: &str, credit: u32) -> XResult<()> {
|
||||
if &self.admin != tx.sender.as_ref().ok_or_else(|| SimpleError::new("Sender is not provided".into()))? {
|
||||
return Err(SimpleError::new(format!("Current user is not admin, {} vs {:?}", self.admin, tx.sender)).into())
|
||||
}
|
||||
if self.issue_amount + credit > self.credit_limit {
|
||||
return Err(SimpleError::new(format!("Issue too much credit, current: {}, issue: {}, limit: {}", self.issue_amount, credit, self.credit_limit)).into());
|
||||
}
|
||||
match self.credit.get_mut(receiver) {
|
||||
None => { self.credit.insert(receiver.to_owned(), credit); },
|
||||
Some(cr) => *cr += credit,
|
||||
}
|
||||
self.issue_amount += credit;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn transfer(&mut self, tx: &Transaction, receiver: &str, credit: u32) -> XResult<()> {
|
||||
match self.credit.get_mut(tx.sender.as_ref().ok_or_else(|| SimpleError::new("Sender is not provided".into()))?) {
|
||||
None => return Err(SimpleError::new(format!("Have not enough credit: {:?}", tx.sender)).into()),
|
||||
Some(cr) => {
|
||||
if *cr >= credit {
|
||||
*cr -= credit;
|
||||
match self.credit.get_mut(receiver) {
|
||||
None => { self.credit.insert(receiver.to_owned(), credit); },
|
||||
Some(receiver_credit) => *receiver_credit += credit,
|
||||
}
|
||||
} else {
|
||||
return Err(SimpleError::new(format!("Have not enough credit: {:?}", tx.sender)).into());
|
||||
}
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get(&self, _tx: &Transaction, account: &str) -> u32 {
|
||||
match self.credit.get(account) {
|
||||
None => 0,
|
||||
Some(receiver_credit) => *receiver_credit,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load(name: &str) -> XResult<Self> {
|
||||
let json = fs::read_to_string(name)?;
|
||||
serde_json::from_str(&json).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
pub fn save(name: &str, c: &CreditContract) -> XResult<()> {
|
||||
if let Ok(_) = File::open(name) {
|
||||
return Err(SimpleError::new(format!("File exists: {}", name)).into());
|
||||
}
|
||||
fs::write(name, serde_json::to_string(c)?.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
31
__crypto/simple_contract/src/main.rs
Normal file
31
__crypto/simple_contract/src/main.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
pub mod util;
|
||||
pub mod tx;
|
||||
pub mod credit;
|
||||
|
||||
use std::str::FromStr;
|
||||
use secp256k1::{Message, Secp256k1, Signature};
|
||||
use util::*;
|
||||
|
||||
fn main() -> XResult<()> {
|
||||
let (pri_key, pub_key) = make_key_pair();
|
||||
println!("{:?}", pri_key);
|
||||
println!("{:?}", pub_key);
|
||||
println!("{:?}", JsonKeyPair::from(pri_key, pub_key).to_json());
|
||||
println!("{:?}", JsonKeyPair::from(pri_key, pub_key).to_key_pair());
|
||||
println!("{}", make_btc_address(&pub_key));
|
||||
|
||||
let p256k1 = Secp256k1::new();
|
||||
let s =calc_sha256("hello".as_bytes());
|
||||
let message = Message::from_slice(&s)?;
|
||||
|
||||
let sign = p256k1.sign(&message, &pri_key);
|
||||
let sign_hex = format!("{}", sign);
|
||||
println!("{}", sign_hex);
|
||||
|
||||
let sig = Signature::from_str(&sign_hex)?;
|
||||
|
||||
let result = p256k1.verify(&message, &sig, &pub_key);
|
||||
println!("{:?}", result);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
76
__crypto/simple_contract/src/tx.rs
Normal file
76
__crypto/simple_contract/src/tx.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
use secp256k1::{Message, PublicKey, Secp256k1, SecretKey, Signature};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use std::str::FromStr;
|
||||
use crate::util::{SimpleError, XResult, calc_sha256, make_btc_address};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Transaction {
|
||||
pub body: String,
|
||||
pub sender: Option<String>,
|
||||
pub signature: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct TransactionBody {
|
||||
pub timestamp: u64,
|
||||
pub nonce: String,
|
||||
pub contract: String,
|
||||
pub action: String,
|
||||
pub parameters: String,
|
||||
}
|
||||
|
||||
impl Transaction {
|
||||
pub fn from_json(transaction_json: &str) -> XResult<Self> {
|
||||
serde_json::from_str(transaction_json).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
pub fn from_transaction_body(transaction_body: &TransactionBody) -> XResult<Self> {
|
||||
Ok(Self {
|
||||
body: serde_json::to_string(transaction_body)?,
|
||||
sender: None,
|
||||
signature: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_body(&self) -> XResult<TransactionBody> {
|
||||
serde_json::from_str(&self.body).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
pub fn is_signed(&self) -> bool {
|
||||
self.signature.is_some()
|
||||
}
|
||||
|
||||
pub fn sign(&mut self, sender: &str, priv_key: &SecretKey) -> XResult<()> {
|
||||
if self.signature.is_some() {
|
||||
Err(SimpleError::new("Transaction is signed!".into()).into())
|
||||
} else {
|
||||
let message = self.get_body_message()?;
|
||||
let sign = Secp256k1::new().sign(&message, priv_key);
|
||||
self.sender = Some(sender.into());
|
||||
self.signature = Some(format!("{}", sign));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify(&self, pub_key: &PublicKey) -> XResult<()> {
|
||||
match (&self.sender, &self.signature) {
|
||||
(None, None) | (None, Some(_)) | (Some(_), None) => {
|
||||
Err(SimpleError::new("Transaction has no sender or not signed!".into()).into())
|
||||
},
|
||||
(Some(sender), Some(sign_hex)) => {
|
||||
let address = make_btc_address(pub_key);
|
||||
if &address != sender {
|
||||
return Err(SimpleError::new("".into()).into());
|
||||
}
|
||||
let message = self.get_body_message()?;
|
||||
let sig = Signature::from_str(sign_hex)?;
|
||||
Secp256k1::new().verify(&message, &sig, pub_key).map_err(|e| e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_body_message(&self) -> XResult<Message> {
|
||||
let digest= calc_sha256(self.body.as_bytes());
|
||||
Message::from_slice(&digest).map_err(|e| e.into())
|
||||
}
|
||||
}
|
||||
89
__crypto/simple_contract/src/util.rs
Normal file
89
__crypto/simple_contract/src/util.rs
Normal file
@@ -0,0 +1,89 @@
|
||||
use rand::rngs::OsRng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use secp256k1::{Secp256k1, SecretKey, key::PublicKey};
|
||||
use sha2::Sha256;
|
||||
use ripemd160::Ripemd160;
|
||||
use digest::{ Input, FixedOutput };
|
||||
use std::{fmt::Display, str::FromStr};
|
||||
use std::error::Error;
|
||||
|
||||
pub type XResult<T> = Result<T, Box<dyn Error>>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SimpleError {
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
impl SimpleError {
|
||||
pub fn new(message: String) -> Self {
|
||||
Self { message }
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SimpleError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "SimpleErorr, message: {}", self.message)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for SimpleError {}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct JsonKeyPair {
|
||||
pub pri_key: String,
|
||||
pub pub_key: String,
|
||||
}
|
||||
|
||||
impl JsonKeyPair {
|
||||
pub fn from(pri_key: SecretKey, pub_key: PublicKey) -> Self {
|
||||
JsonKeyPair {
|
||||
pri_key: format!("{}", pri_key),
|
||||
pub_key: format!("{}", pub_key),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_json(&self) -> XResult<String> {
|
||||
serde_json::to_string(self).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
pub fn to_key_pair(&self) -> XResult<(SecretKey, PublicKey)> {
|
||||
let pri_key = SecretKey::from_str(&self.pri_key)?;
|
||||
let pub_key = PublicKey::from_str(&self.pub_key)?;
|
||||
Ok((pri_key, pub_key))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_key_pair() -> (SecretKey, PublicKey) {
|
||||
let secp = Secp256k1::new();
|
||||
let mut rng = OsRng::new().expect("OsRng");
|
||||
let (secret_key, public_key) = secp.generate_keypair(&mut rng);
|
||||
(secret_key, public_key)
|
||||
}
|
||||
|
||||
pub fn make_btc_address(public_key: &PublicKey) -> String {
|
||||
let public_key_bytes = public_key.serialize_uncompressed().to_vec();
|
||||
let riphemd160_sha256_pub_key = calc_ripemd160(&calc_sha256(&public_key_bytes));
|
||||
let mut btc_addr = Vec::<u8>::with_capacity(25);
|
||||
btc_addr.push(0x00 as u8);
|
||||
btc_addr.extend_from_slice(&riphemd160_sha256_pub_key);
|
||||
let checksum = &calc_sha256(&calc_sha256(&btc_addr))[0..4];
|
||||
btc_addr.extend_from_slice(checksum);
|
||||
bs58::encode(&btc_addr).into_string()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn calc_sha256(i: &[u8]) -> Vec<u8> {
|
||||
calc_hash(Sha256::default(), i)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn calc_ripemd160(i: &[u8]) -> Vec<u8> {
|
||||
calc_hash(Ripemd160::default(), i)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn calc_hash<T>(mut hasher: T, i: &[u8]) -> Vec<u8> where T: Input + FixedOutput {
|
||||
hasher.input(&i);
|
||||
hasher.fixed_result().to_vec()
|
||||
}
|
||||
Reference in New Issue
Block a user