mirror of
https://github.com/jwinterm/grin-wallet.git
synced 2026-10-17 15:21:39 +02:00
* Simple contracts restructured v3 (#675) * Add prototype contract implementation Lacks: - Payment proofs (add early payment proofs) - Tests - Better structure * Separate contract utilities * Simplify the main setup/sign code flow * Remove commented initial_sec_key assignment * Simplify sign::compute function * Add logic for "removal" of secret keys from the context * Further simplify a bit sign and setup * No need for mutable context when adding outputs * Refactor the commented code (lol) * Refactor a bit * tmp --------- Co-authored-by: oryhp <gtrphyro@gmail.com> * contract test fixes (#676) * [Contracts] Experimental Slate v5 (#677) * beginning to add and modify slate version 5 * completion of conversions from V5 to V4 Slate * timestamp and memo fields * upgrade/downgrade serialization of v5 slates * add v5 binary slate versions + start of tests * add bin slate ser/deser to tests * ensure serialization of timestamp always excludes milliseconds * start to update v5 tests, update v5 documentation * add fn to generate populated internal slate for conversion testing * add basic tests to convert all slate versions * update * commit cargo.lock * [WIP] [Contracts] Early payment proofs (#681) * add types and beginnings of signature utils * add proof serialization * serialisation of proof data + signature operation * add serialization type for invoice proof + separate bin wrapper version * add witness data + serializion to invoice payment proof, insert verfication functions in place in order to begin verification testing * tests and infrastructure in place for validation * verification of promise sig * added verification of promise signature, infrastructure up to the point where a signature must be subtracted * attempting to figure out differences between recipient nonce that's getting stored and calculated recipient nonce * implementation of witness verification function, retrieve relevant values and re-validate derived recipient partial signature * move stored portion of invoice proof into core types for storage, need to rename invoice proof * define/refine the stored portion of payment proofs type 2? * Folding all proof data into tx log entry storage * back to importing master * remove cargo files from diffs * remove a lot of extra debug output * return proof witness as part of proof retrieval, define json serialization of invoice proof + witness fields * finish adding verification steps to foreign API * remove redundant promise sig field * move lcation of sign/verify calls * Replace Azure Pipelines with Github Actions (#688) * Update CI Badge on README.MD (#690) * Trigger CI on push and pull request (#693) * Update versioning to 5.2.0-beta.1 against grin 5.2.0-beta.3 (#691) * update versioning to 5.2.0-beta.1 against grin 5.2.0-beta.3 * tweak for CI trigger --------- Co-authored-by: Quentin Le Sceller <q.lesceller@gmail.com> --------- Co-authored-by: Quentin Le Sceller <q.lesceller@gmail.com> * fix Cargo.lock * Contracts - Test fixes + warning cleanups (#694) * clean up warnings in libwallet crate * clean up warnings in controller crate * update all contract tests with awareness of new proof structure * added explicit payment proof tests for both rsr and srs (#695) * Basic mwmixnet send (#696) * integrating onion library * updates and changes to support newly included mwmixnet types * add (incorrect) owner api function * turn off test for now * switch working grin branch to master * fix doctests for build * update cargo lock in attempt to fix croaring build on CI server * update cargo lock with upstream thiserror crate * update test dependency for croaring * [Contracts] Add self spend transaction state (#697) * Add self spend transaction state * subtle errors with output states and tx lookups - fixes * [Contracts] Slatepack v5 Deserialization fix (#698) * add V5 deserialization test + fixes * clarify comment * upwrap fix during v4 deserialization * further unwrap removal * update cargo lock, use latest grin packages * addition of self-spend cancel + tests (#699) * add tests + legacy self send cancel (#702) * [Contracts] Cancel self-spend TX Pt. 2 (#703) * add tests + legacy self send cancel * add missing file * Update grin to latest master, change versioning label to include contracts (#712) * Add owner_api_listen_interface as hidden configuration field (#714) * updates for rust 1.80 * updates for rust 1.80 --------- Co-authored-by: oryhp <gtrphyro@gmail.com> Co-authored-by: Quentin Le Sceller <q.lesceller@gmail.com>
199 lines
6.6 KiB
Rust
199 lines
6.6 KiB
Rust
// Copyright 2021 The Grin Developers
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// A note on encoding efficiency: 0.75 for Base64, 0.744 for Base62, 0.732 for Base58
|
|
// slatepack uses a modified Base58Check encoding to create armored slate payloads:
|
|
// 1. Take first four bytes of SHA256(SHA256(slate.as_bytes()))
|
|
// 2. Concatenate result of step 1 and slate.as_bytes()
|
|
// 3. Base58 encode bytes from step 2
|
|
// Finally add armor framing and space/newline formatting as desired
|
|
|
|
use crate::Error;
|
|
use grin_core::global::max_tx_weight;
|
|
use grin_wallet_util::byte_ser;
|
|
use regex::Regex;
|
|
use sha2::{Digest, Sha256};
|
|
use std::str;
|
|
|
|
use super::types::{Slatepack, SlatepackBin};
|
|
|
|
// Framing and formatting for slate armor
|
|
pub static HEADER: &str = "BEGINSLATEPACK.";
|
|
static FOOTER: &str = ". ENDSLATEPACK.";
|
|
const WORD_LENGTH: usize = 15;
|
|
const WORDS_PER_LINE: usize = 200;
|
|
const WEIGHT_RATIO: u64 = 32;
|
|
|
|
/// Maximum size for an armored Slatepack file
|
|
pub fn max_size() -> u64 {
|
|
max_tx_weight()
|
|
.saturating_mul(WEIGHT_RATIO)
|
|
.saturating_add(HEADER.len() as u64)
|
|
.saturating_add(FOOTER.len() as u64)
|
|
}
|
|
|
|
/// Minimum size for an armored Slatepack file or stream
|
|
pub fn min_size() -> u64 {
|
|
HEADER.len() as u64
|
|
}
|
|
|
|
lazy_static! {
|
|
static ref HEADER_REGEX: Regex =
|
|
Regex::new(concat!(r"^[>\n\r\t ]*BEGINSLATEPACK[>\n\r\t ]*$")).unwrap();
|
|
static ref FOOTER_REGEX: Regex =
|
|
Regex::new(concat!(r"^[>\n\r\t ]*ENDSLATEPACK[>\n\r\t ]*$")).unwrap();
|
|
static ref WHITESPACE_LIST: [u8; 5] = [b'>', b'\n', b'\r', b'\t', b' '];
|
|
}
|
|
|
|
/// Wrapper for associated functions
|
|
pub struct SlatepackArmor;
|
|
|
|
impl SlatepackArmor {
|
|
/// Decode an armored Slatepack
|
|
pub fn decode(armor_bytes: &[u8]) -> Result<Vec<u8>, Error> {
|
|
// Collect the bytes up to the first period, this is the header
|
|
let header_bytes = armor_bytes
|
|
.iter()
|
|
.take_while(|byte| **byte != b'.')
|
|
.cloned()
|
|
.collect::<Vec<u8>>();
|
|
// Verify the header...
|
|
check_header(&header_bytes)?;
|
|
// Get the length of the header
|
|
let header_len = header_bytes.len() + 1;
|
|
// Skip the length of the header to read for the payload until the next period
|
|
let payload_bytes = armor_bytes[header_len as usize..]
|
|
.iter()
|
|
.take_while(|byte| **byte != b'.')
|
|
.cloned()
|
|
.collect::<Vec<u8>>();
|
|
// Get length of the payload to check the footer framing
|
|
let payload_len = payload_bytes.len();
|
|
// Get footer bytes and verify them
|
|
let consumed_bytes = header_len + payload_len + 1;
|
|
let footer_bytes = armor_bytes[consumed_bytes as usize..]
|
|
.iter()
|
|
.take_while(|byte| **byte != b'.')
|
|
.cloned()
|
|
.collect::<Vec<u8>>();
|
|
check_footer(&footer_bytes)?;
|
|
// Clean up the payload bytes to be deserialized
|
|
let clean_payload = payload_bytes
|
|
.iter()
|
|
.filter(|byte| !WHITESPACE_LIST.contains(byte))
|
|
.cloned()
|
|
.collect::<Vec<u8>>();
|
|
// Decode payload from base58
|
|
let base_decode = bs58::decode(&clean_payload)
|
|
.into_vec()
|
|
.map_err(|_| Error::SlatepackDeser("Bad bytes".into()))?;
|
|
let error_code = &base_decode[0..4];
|
|
let slatepack_bytes = &base_decode[4..];
|
|
// Make sure the error check code is valid for the slate data
|
|
error_check(error_code, slatepack_bytes)?;
|
|
// Return slate as binary or string
|
|
Ok(slatepack_bytes.to_vec())
|
|
}
|
|
|
|
/// Encode an armored slatepack
|
|
pub fn encode(slatepack: &Slatepack) -> Result<String, Error> {
|
|
let slatepack_bytes = byte_ser::to_bytes(&SlatepackBin(slatepack.clone()))
|
|
.map_err(|_| Error::SlatepackSer)?;
|
|
let encoded_slatepack = base58check(&slatepack_bytes)?;
|
|
let formatted_slatepack = format_slatepack(&format!("{}{}", HEADER, encoded_slatepack))?;
|
|
Ok(format!("{}{}\n", formatted_slatepack, FOOTER))
|
|
}
|
|
}
|
|
|
|
// Takes an error check code and a slate binary and verifies that the code was generated from slate
|
|
fn error_check(error_code: &[u8], slate_bytes: &[u8]) -> Result<(), Error> {
|
|
let new_check = generate_check(slate_bytes)?;
|
|
if error_code.iter().eq(new_check.iter()) {
|
|
Ok(())
|
|
} else {
|
|
Err(Error::InvalidSlatepackData(
|
|
"Bad slate error code- some data was corrupted".to_string(),
|
|
))
|
|
}
|
|
}
|
|
|
|
// Checks header framing bytes and returns an error if they are invalid
|
|
fn check_header(header: &[u8]) -> Result<(), Error> {
|
|
let framing = str::from_utf8(header).map_err(|_| Error::SlatepackDeser("Bad bytes".into()))?;
|
|
if HEADER_REGEX.is_match(framing) {
|
|
Ok(())
|
|
} else {
|
|
Err(Error::InvalidSlatepackData("Bad armor header".to_string()))
|
|
}
|
|
}
|
|
|
|
// Checks footer framing bytes and returns an error if they are invalid
|
|
fn check_footer(footer: &[u8]) -> Result<(), Error> {
|
|
let framing = str::from_utf8(footer).map_err(|_| Error::SlatepackDeser("Bad bytes".into()))?;
|
|
if FOOTER_REGEX.is_match(framing) {
|
|
Ok(())
|
|
} else {
|
|
Err(Error::InvalidSlatepackData("Bad armor footer".to_string()))
|
|
}
|
|
}
|
|
|
|
// MODIFIED Base58Check encoding for slate bytes
|
|
fn base58check(slate: &[u8]) -> Result<String, Error> {
|
|
// Serialize the slate json string to a vector of bytes
|
|
let mut slate_bytes: Vec<u8> = slate.to_vec();
|
|
// Get the four byte checksum for the slate binary
|
|
let mut check_bytes: Vec<u8> = generate_check(&slate_bytes)?;
|
|
// Make a new buffer and concatenate checksum bytes and slate bytes
|
|
let mut slate_buf = Vec::new();
|
|
slate_buf.append(&mut check_bytes);
|
|
slate_buf.append(&mut slate_bytes);
|
|
// Encode the slate buffer containing the slate binary and checksum bytes as Base58
|
|
let b58_slate = bs58::encode(slate_buf).into_string();
|
|
Ok(b58_slate)
|
|
}
|
|
|
|
// Adds human readable formatting to the slate payload for armoring
|
|
fn format_slatepack(slatepack: &str) -> Result<String, Error> {
|
|
let formatter = slatepack
|
|
.chars()
|
|
.enumerate()
|
|
.flat_map(|(i, c)| {
|
|
if i != 0 && i % WORD_LENGTH == 0 {
|
|
if WORDS_PER_LINE != 0 && i % (WORD_LENGTH * WORDS_PER_LINE) == 0 {
|
|
Some('\n')
|
|
} else {
|
|
Some(' ')
|
|
}
|
|
} else {
|
|
None
|
|
}
|
|
.into_iter()
|
|
.chain(std::iter::once(c))
|
|
})
|
|
.collect::<String>();
|
|
Ok(formatter)
|
|
}
|
|
|
|
// Returns the first four bytes of a double sha256 hash of some bytes
|
|
fn generate_check(payload: &[u8]) -> Result<Vec<u8>, Error> {
|
|
let mut first_hash = Sha256::new();
|
|
first_hash.update(payload);
|
|
let mut second_hash = Sha256::new();
|
|
second_hash.update(first_hash.finalize());
|
|
let checksum = second_hash.finalize();
|
|
let check_bytes: Vec<u8> = checksum[0..4].to_vec();
|
|
Ok(check_bytes)
|
|
}
|