Files
grin-wallet/libwallet/src/slatepack/armor.rs
T
cdd63fb565 [CONTRACTS] Contracts branch update for 1.80 (#718)
* 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>
2024-08-27 12:30:22 +01:00

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)
}