Rename the coins folder to networks (#583)

* Rename the coins folder to networks

Ethereum isn't a coin. It's a network.

Resolves #357.

* More renames of coins -> networks in orchestration

* Correct paths in tests/

* cargo fmt
This commit is contained in:
Luke Parker
2024-07-18 12:16:45 -07:00
committed by GitHub
parent 40cc180853
commit 7d2d739042
244 changed files with 102 additions and 99 deletions

View File

@@ -0,0 +1,152 @@
use std_shims::{
vec,
vec::Vec,
io::{self, Read, Write},
};
use crate::{
io::*,
primitives::keccak256,
merkle::merkle_root,
transaction::{Input, Transaction},
};
const CORRECT_BLOCK_HASH_202612: [u8; 32] =
hex_literal::hex!("426d16cff04c71f8b16340b722dc4010a2dd3831c22041431f772547ba6e331a");
const EXISTING_BLOCK_HASH_202612: [u8; 32] =
hex_literal::hex!("bbd604d2ba11ba27935e006ed39c9bfdd99b76bf4a50654bc1e1e61217962698");
/// A Monero block's header.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct BlockHeader {
/// The hard fork of the protocol this block follows.
///
/// Per the C++ codebase, this is the `major_version`.
pub hardfork_version: u8,
/// A signal for a proposed hard fork.
///
/// Per the C++ codebase, this is the `minor_version`.
pub hardfork_signal: u8,
/// Seconds since the epoch.
pub timestamp: u64,
/// The previous block's hash.
pub previous: [u8; 32],
/// The nonce used to mine the block.
///
/// Miners should increment this while attempting to find a block with a hash satisfying the PoW
/// rules.
pub nonce: u32,
}
impl BlockHeader {
/// Write the BlockHeader.
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
write_varint(&self.hardfork_version, w)?;
write_varint(&self.hardfork_signal, w)?;
write_varint(&self.timestamp, w)?;
w.write_all(&self.previous)?;
w.write_all(&self.nonce.to_le_bytes())
}
/// Serialize the BlockHeader to a `Vec<u8>`.
pub fn serialize(&self) -> Vec<u8> {
let mut serialized = vec![];
self.write(&mut serialized).unwrap();
serialized
}
/// Read a BlockHeader.
pub fn read<R: Read>(r: &mut R) -> io::Result<BlockHeader> {
Ok(BlockHeader {
hardfork_version: read_varint(r)?,
hardfork_signal: read_varint(r)?,
timestamp: read_varint(r)?,
previous: read_bytes(r)?,
nonce: read_bytes(r).map(u32::from_le_bytes)?,
})
}
}
/// A Monero block.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Block {
/// The block's header.
pub header: BlockHeader,
/// The miner's transaction.
pub miner_transaction: Transaction,
/// The transactions within this block.
pub transactions: Vec<[u8; 32]>,
}
impl Block {
/// The zero-index position of this block within the blockchain.
///
/// This information comes from the Block's miner transaction. If the miner transaction isn't
/// structed as expected, this will return None.
pub fn number(&self) -> Option<usize> {
match &self.miner_transaction {
Transaction::V1 { prefix, .. } | Transaction::V2 { prefix, .. } => {
match prefix.inputs.first() {
Some(Input::Gen(number)) => Some(*number),
_ => None,
}
}
}
}
/// Write the Block.
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
self.header.write(w)?;
self.miner_transaction.write(w)?;
write_varint(&self.transactions.len(), w)?;
for tx in &self.transactions {
w.write_all(tx)?;
}
Ok(())
}
/// Serialize the Block to a `Vec<u8>`.
pub fn serialize(&self) -> Vec<u8> {
let mut serialized = vec![];
self.write(&mut serialized).unwrap();
serialized
}
/// Serialize the block as required for the proof of work hash.
///
/// This is distinct from the serialization required for the block hash. To get the block hash,
/// use the [`Block::hash`] function.
pub fn serialize_pow_hash(&self) -> Vec<u8> {
let mut blob = self.header.serialize();
blob.extend_from_slice(&merkle_root(self.miner_transaction.hash(), &self.transactions));
write_varint(&(1 + u64::try_from(self.transactions.len()).unwrap()), &mut blob).unwrap();
blob
}
/// Get the hash of this block.
pub fn hash(&self) -> [u8; 32] {
let mut hashable = self.serialize_pow_hash();
// Monero pre-appends a VarInt of the block-to-hash'ss length before getting the block hash,
// but doesn't do this when getting the proof of work hash :)
let mut hashing_blob = Vec::with_capacity(9 + hashable.len());
write_varint(&u64::try_from(hashable.len()).unwrap(), &mut hashing_blob).unwrap();
hashing_blob.append(&mut hashable);
let hash = keccak256(hashing_blob);
if hash == CORRECT_BLOCK_HASH_202612 {
return EXISTING_BLOCK_HASH_202612;
};
hash
}
/// Read a Block.
pub fn read<R: Read>(r: &mut R) -> io::Result<Block> {
Ok(Block {
header: BlockHeader::read(r)?,
miner_transaction: Transaction::read(r)?,
transactions: (0_usize .. read_varint(r)?)
.map(|_| read_bytes(r))
.collect::<Result<_, _>>()?,
})
}
}

View File

@@ -0,0 +1,39 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")]
#![deny(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
pub use monero_io as io;
pub use monero_generators as generators;
pub use monero_primitives as primitives;
mod merkle;
/// Ring Signature structs and functionality.
pub mod ring_signatures;
/// RingCT structs and functionality.
pub mod ringct;
/// Transaction structs and functionality.
pub mod transaction;
/// Block structs and functionality.
pub mod block;
#[cfg(test)]
mod tests;
/// The minimum amount of blocks an output is locked for.
///
/// If Monero suffered a re-organization, any transactions which selected decoys belonging to
/// recent blocks would become invalidated. Accordingly, transactions must use decoys which are
/// presumed to not be invalidated in the future. If wallets only selected n-block-old outputs as
/// decoys, then any ring member within the past n blocks would have to be the real spend.
/// Preventing this at the consensus layer ensures privacy and integrity.
pub const DEFAULT_LOCK_WINDOW: usize = 10;
/// The minimum amount of blocks a coinbase output is locked for.
pub const COINBASE_LOCK_WINDOW: usize = 60;
/// Monero's block time target, in seconds.
pub const BLOCK_TIME: usize = 120;

View File

@@ -0,0 +1,55 @@
use std_shims::vec::Vec;
use crate::primitives::keccak256;
pub(crate) fn merkle_root(root: [u8; 32], leafs: &[[u8; 32]]) -> [u8; 32] {
match leafs.len() {
0 => root,
1 => keccak256([root, leafs[0]].concat()),
_ => {
let mut hashes = Vec::with_capacity(1 + leafs.len());
hashes.push(root);
hashes.extend(leafs);
// Monero preprocess this so the length is a power of 2
let mut high_pow_2 = 4; // 4 is the lowest value this can be
while high_pow_2 < hashes.len() {
high_pow_2 *= 2;
}
let low_pow_2 = high_pow_2 / 2;
// Merge right-most hashes until we're at the low_pow_2
{
let overage = hashes.len() - low_pow_2;
let mut rightmost = hashes.drain((low_pow_2 - overage) ..);
// This is true since we took overage from beneath and above low_pow_2, taking twice as
// many elements as overage
debug_assert_eq!(rightmost.len() % 2, 0);
let mut paired_hashes = Vec::with_capacity(overage);
while let Some(left) = rightmost.next() {
let right = rightmost.next().unwrap();
paired_hashes.push(keccak256([left.as_ref(), &right].concat()));
}
drop(rightmost);
hashes.extend(paired_hashes);
assert_eq!(hashes.len(), low_pow_2);
}
// Do a traditional pairing off
let mut new_hashes = Vec::with_capacity(hashes.len() / 2);
while hashes.len() > 1 {
let mut i = 0;
while i < hashes.len() {
new_hashes.push(keccak256([hashes[i], hashes[i + 1]].concat()));
i += 2;
}
hashes = new_hashes;
new_hashes = Vec::with_capacity(hashes.len() / 2);
}
hashes[0]
}
}
}

View File

@@ -0,0 +1,101 @@
use std_shims::{
io::{self, *},
vec::Vec,
};
use zeroize::Zeroize;
use curve25519_dalek::{EdwardsPoint, Scalar};
use crate::{io::*, generators::hash_to_point, primitives::keccak256_to_scalar};
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
pub(crate) struct Signature {
#[cfg(test)]
pub(crate) c: Scalar,
#[cfg(test)]
pub(crate) s: Scalar,
#[cfg(not(test))]
c: Scalar,
#[cfg(not(test))]
s: Scalar,
}
impl Signature {
fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
write_scalar(&self.c, w)?;
write_scalar(&self.s, w)?;
Ok(())
}
fn read<R: Read>(r: &mut R) -> io::Result<Signature> {
Ok(Signature { c: read_scalar(r)?, s: read_scalar(r)? })
}
}
/// A ring signature.
///
/// This was used by the original Cryptonote transaction protocol and was deprecated with RingCT.
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
pub struct RingSignature {
#[cfg(test)]
pub(crate) sigs: Vec<Signature>,
#[cfg(not(test))]
sigs: Vec<Signature>,
}
impl RingSignature {
/// Write the RingSignature.
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
for sig in &self.sigs {
sig.write(w)?;
}
Ok(())
}
/// Read a RingSignature.
pub fn read<R: Read>(members: usize, r: &mut R) -> io::Result<RingSignature> {
Ok(RingSignature { sigs: read_raw_vec(Signature::read, members, r)? })
}
/// Verify the ring signature.
pub fn verify(&self, msg: &[u8; 32], ring: &[EdwardsPoint], key_image: &EdwardsPoint) -> bool {
if ring.len() != self.sigs.len() {
return false;
}
let mut buf = Vec::with_capacity(32 + (2 * 32 * ring.len()));
buf.extend_from_slice(msg);
let mut sum = Scalar::ZERO;
for (ring_member, sig) in ring.iter().zip(&self.sigs) {
/*
The traditional Schnorr signature is:
r = sample()
c = H(r G || m)
s = r - c x
Verified as:
s G + c A == R
Each ring member here performs a dual-Schnorr signature for:
s G + c A
s HtP(A) + c K
Where the transcript is pushed both these values, r G, r HtP(A) for the real spend.
This also serves as a DLEq proof between the key and the key image.
Checking sum(c) == H(transcript) acts a disjunction, where any one of the `c`s can be
modified to cause the intended sum, if and only if a corresponding `s` value is known.
*/
#[allow(non_snake_case)]
let Li = EdwardsPoint::vartime_double_scalar_mul_basepoint(&sig.c, ring_member, &sig.s);
buf.extend_from_slice(Li.compress().as_bytes());
#[allow(non_snake_case)]
let Ri = (sig.s * hash_to_point(ring_member.compress().to_bytes())) + (sig.c * key_image);
buf.extend_from_slice(Ri.compress().as_bytes());
sum += sig.c;
}
sum == keccak256_to_scalar(buf)
}
}

View File

@@ -0,0 +1,470 @@
use std_shims::{
vec,
vec::Vec,
io::{self, Read, Write},
};
use zeroize::Zeroize;
use curve25519_dalek::edwards::EdwardsPoint;
pub use monero_mlsag as mlsag;
pub use monero_clsag as clsag;
pub use monero_borromean as borromean;
pub use monero_bulletproofs as bulletproofs;
use crate::{
io::*,
ringct::{mlsag::Mlsag, clsag::Clsag, borromean::BorromeanRange, bulletproofs::Bulletproof},
};
/// An encrypted amount.
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum EncryptedAmount {
/// The original format for encrypted amounts.
Original {
/// A mask used with a mask derived from the shared secret to encrypt the amount.
mask: [u8; 32],
/// The amount, as a scalar, encrypted.
amount: [u8; 32],
},
/// The "compact" format for encrypted amounts.
Compact {
/// The amount, as a u64, encrypted.
amount: [u8; 8],
},
}
impl EncryptedAmount {
/// Read an EncryptedAmount from a reader.
pub fn read<R: Read>(compact: bool, r: &mut R) -> io::Result<EncryptedAmount> {
Ok(if !compact {
EncryptedAmount::Original { mask: read_bytes(r)?, amount: read_bytes(r)? }
} else {
EncryptedAmount::Compact { amount: read_bytes(r)? }
})
}
/// Write the EncryptedAmount to a writer.
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
match self {
EncryptedAmount::Original { mask, amount } => {
w.write_all(mask)?;
w.write_all(amount)
}
EncryptedAmount::Compact { amount } => w.write_all(amount),
}
}
}
/// The type of the RingCT data.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
pub enum RctType {
/// One MLSAG for multiple inputs and Borromean range proofs.
///
/// This aligns with RCTTypeFull.
AggregateMlsagBorromean,
// One MLSAG for each input and a Borromean range proof.
///
/// This aligns with RCTTypeSimple.
MlsagBorromean,
// One MLSAG for each input and a Bulletproof.
///
/// This aligns with RCTTypeBulletproof.
MlsagBulletproofs,
/// One MLSAG for each input and a Bulletproof, yet using EncryptedAmount::Compact.
///
/// This aligns with RCTTypeBulletproof2.
MlsagBulletproofsCompactAmount,
/// One CLSAG for each input and a Bulletproof.
///
/// This aligns with RCTTypeCLSAG.
ClsagBulletproof,
/// One CLSAG for each input and a Bulletproof+.
///
/// This aligns with RCTTypeBulletproofPlus.
ClsagBulletproofPlus,
}
impl From<RctType> for u8 {
fn from(rct_type: RctType) -> u8 {
match rct_type {
RctType::AggregateMlsagBorromean => 1,
RctType::MlsagBorromean => 2,
RctType::MlsagBulletproofs => 3,
RctType::MlsagBulletproofsCompactAmount => 4,
RctType::ClsagBulletproof => 5,
RctType::ClsagBulletproofPlus => 6,
}
}
}
impl TryFrom<u8> for RctType {
type Error = ();
fn try_from(byte: u8) -> Result<Self, ()> {
Ok(match byte {
1 => RctType::AggregateMlsagBorromean,
2 => RctType::MlsagBorromean,
3 => RctType::MlsagBulletproofs,
4 => RctType::MlsagBulletproofsCompactAmount,
5 => RctType::ClsagBulletproof,
6 => RctType::ClsagBulletproofPlus,
_ => Err(())?,
})
}
}
impl RctType {
/// True if this RctType uses compact encrypted amounts, false otherwise.
pub fn compact_encrypted_amounts(&self) -> bool {
match self {
RctType::AggregateMlsagBorromean | RctType::MlsagBorromean | RctType::MlsagBulletproofs => {
false
}
RctType::MlsagBulletproofsCompactAmount |
RctType::ClsagBulletproof |
RctType::ClsagBulletproofPlus => true,
}
}
/// True if this RctType uses a Bulletproof, false otherwise.
pub(crate) fn bulletproof(&self) -> bool {
match self {
RctType::MlsagBulletproofs |
RctType::MlsagBulletproofsCompactAmount |
RctType::ClsagBulletproof => true,
RctType::AggregateMlsagBorromean |
RctType::MlsagBorromean |
RctType::ClsagBulletproofPlus => false,
}
}
/// True if this RctType uses a Bulletproof+, false otherwise.
pub(crate) fn bulletproof_plus(&self) -> bool {
match self {
RctType::ClsagBulletproofPlus => true,
RctType::AggregateMlsagBorromean |
RctType::MlsagBorromean |
RctType::MlsagBulletproofs |
RctType::MlsagBulletproofsCompactAmount |
RctType::ClsagBulletproof => false,
}
}
}
/// The base of the RingCT data.
///
/// This excludes all proofs (which once initially verified do not need to be kept around) and
/// solely keeps data which either impacts the effects of the transactions or is needed to scan it.
///
/// The one exception for this is `pseudo_outs`, which was originally present here yet moved to
/// RctPrunable in a later hard fork (causing it to be present in both).
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct RctBase {
/// The fee used by this transaction.
pub fee: u64,
/// The re-randomized amount commitments used within inputs.
///
/// This field was deprecated and is empty for modern RctTypes.
pub pseudo_outs: Vec<EdwardsPoint>,
/// The encrypted amounts for the recipients to decrypt.
pub encrypted_amounts: Vec<EncryptedAmount>,
/// The output commitments.
pub commitments: Vec<EdwardsPoint>,
}
impl RctBase {
/// Write the RctBase.
pub fn write<W: Write>(&self, w: &mut W, rct_type: RctType) -> io::Result<()> {
w.write_all(&[u8::from(rct_type)])?;
write_varint(&self.fee, w)?;
if rct_type == RctType::MlsagBorromean {
write_raw_vec(write_point, &self.pseudo_outs, w)?;
}
for encrypted_amount in &self.encrypted_amounts {
encrypted_amount.write(w)?;
}
write_raw_vec(write_point, &self.commitments, w)
}
/// Read a RctBase.
pub fn read<R: Read>(
inputs: usize,
outputs: usize,
r: &mut R,
) -> io::Result<Option<(RctType, RctBase)>> {
let rct_type = read_byte(r)?;
if rct_type == 0 {
return Ok(None);
}
let rct_type =
RctType::try_from(rct_type).map_err(|()| io::Error::other("invalid RCT type"))?;
match rct_type {
RctType::AggregateMlsagBorromean | RctType::MlsagBorromean => {}
RctType::MlsagBulletproofs |
RctType::MlsagBulletproofsCompactAmount |
RctType::ClsagBulletproof |
RctType::ClsagBulletproofPlus => {
if outputs == 0 {
// Because the Bulletproofs(+) layout must be canonical, there must be 1 Bulletproof if
// Bulletproofs are in use
// If there are Bulletproofs, there must be a matching amount of outputs, implicitly
// banning 0 outputs
// Since HF 12 (CLSAG being 13), a 2-output minimum has also been enforced
Err(io::Error::other("RCT with Bulletproofs(+) had 0 outputs"))?;
}
}
}
Ok(Some((
rct_type,
RctBase {
fee: read_varint(r)?,
// Only read pseudo_outs if they have yet to be moved to RctPrunable
// This would apply to AggregateMlsagBorromean and MlsagBorromean, except
// AggregateMlsagBorromean doesn't use pseudo_outs due to using the sum of the output
// commitments directly as the effective singular pseudo-out
pseudo_outs: if rct_type == RctType::MlsagBorromean {
read_raw_vec(read_point, inputs, r)?
} else {
vec![]
},
encrypted_amounts: (0 .. outputs)
.map(|_| EncryptedAmount::read(rct_type.compact_encrypted_amounts(), r))
.collect::<Result<_, _>>()?,
commitments: read_raw_vec(read_point, outputs, r)?,
},
)))
}
}
/// The prunable part of the RingCT data.
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum RctPrunable {
/// An aggregate MLSAG with Borromean range proofs.
AggregateMlsagBorromean {
/// The aggregate MLSAG ring signature.
mlsag: Mlsag,
/// The Borromean range proofs for each output.
borromean: Vec<BorromeanRange>,
},
/// MLSAGs with Borromean range proofs.
MlsagBorromean {
/// The MLSAG ring signatures for each input.
mlsags: Vec<Mlsag>,
/// The Borromean range proofs for each output.
borromean: Vec<BorromeanRange>,
},
/// MLSAGs with Bulletproofs.
MlsagBulletproofs {
/// The MLSAG ring signatures for each input.
mlsags: Vec<Mlsag>,
/// The re-blinded commitments for the outputs being spent.
pseudo_outs: Vec<EdwardsPoint>,
/// The aggregate Bulletproof, proving the outputs are within range.
bulletproof: Bulletproof,
},
/// MLSAGs with Bulletproofs and compact encrypted amounts.
///
/// This has an identical layout to MlsagBulletproofs and is interpreted the exact same way. It's
/// only differentiated to ensure discovery of the correct RctType.
MlsagBulletproofsCompactAmount {
/// The MLSAG ring signatures for each input.
mlsags: Vec<Mlsag>,
/// The re-blinded commitments for the outputs being spent.
pseudo_outs: Vec<EdwardsPoint>,
/// The aggregate Bulletproof, proving the outputs are within range.
bulletproof: Bulletproof,
},
/// CLSAGs with Bulletproofs(+).
Clsag {
/// The CLSAGs for each input.
clsags: Vec<Clsag>,
/// The re-blinded commitments for the outputs being spent.
pseudo_outs: Vec<EdwardsPoint>,
/// The aggregate Bulletproof(+), proving the outputs are within range.
bulletproof: Bulletproof,
},
}
impl RctPrunable {
/// Write the RctPrunable.
pub fn write<W: Write>(&self, w: &mut W, rct_type: RctType) -> io::Result<()> {
match self {
RctPrunable::AggregateMlsagBorromean { borromean, mlsag } => {
write_raw_vec(BorromeanRange::write, borromean, w)?;
mlsag.write(w)
}
RctPrunable::MlsagBorromean { borromean, mlsags } => {
write_raw_vec(BorromeanRange::write, borromean, w)?;
write_raw_vec(Mlsag::write, mlsags, w)
}
RctPrunable::MlsagBulletproofs { bulletproof, mlsags, pseudo_outs } |
RctPrunable::MlsagBulletproofsCompactAmount { bulletproof, mlsags, pseudo_outs } => {
if rct_type == RctType::MlsagBulletproofs {
w.write_all(&1u32.to_le_bytes())?;
} else {
w.write_all(&[1])?;
}
bulletproof.write(w)?;
write_raw_vec(Mlsag::write, mlsags, w)?;
write_raw_vec(write_point, pseudo_outs, w)
}
RctPrunable::Clsag { bulletproof, clsags, pseudo_outs } => {
w.write_all(&[1])?;
bulletproof.write(w)?;
write_raw_vec(Clsag::write, clsags, w)?;
write_raw_vec(write_point, pseudo_outs, w)
}
}
}
/// Serialize the RctPrunable to a `Vec<u8>`.
pub fn serialize(&self, rct_type: RctType) -> Vec<u8> {
let mut serialized = vec![];
self.write(&mut serialized, rct_type).unwrap();
serialized
}
/// Read a RctPrunable.
pub fn read<R: Read>(
rct_type: RctType,
ring_length: usize,
inputs: usize,
outputs: usize,
r: &mut R,
) -> io::Result<RctPrunable> {
Ok(match rct_type {
RctType::AggregateMlsagBorromean => RctPrunable::AggregateMlsagBorromean {
borromean: read_raw_vec(BorromeanRange::read, outputs, r)?,
mlsag: Mlsag::read(ring_length, inputs + 1, r)?,
},
RctType::MlsagBorromean => RctPrunable::MlsagBorromean {
borromean: read_raw_vec(BorromeanRange::read, outputs, r)?,
mlsags: (0 .. inputs).map(|_| Mlsag::read(ring_length, 2, r)).collect::<Result<_, _>>()?,
},
RctType::MlsagBulletproofs | RctType::MlsagBulletproofsCompactAmount => {
let bulletproof = {
if (if rct_type == RctType::MlsagBulletproofs {
u64::from(read_u32(r)?)
} else {
read_varint(r)?
}) != 1
{
Err(io::Error::other("n bulletproofs instead of one"))?;
}
Bulletproof::read(r)?
};
let mlsags =
(0 .. inputs).map(|_| Mlsag::read(ring_length, 2, r)).collect::<Result<_, _>>()?;
let pseudo_outs = read_raw_vec(read_point, inputs, r)?;
if rct_type == RctType::MlsagBulletproofs {
RctPrunable::MlsagBulletproofs { bulletproof, mlsags, pseudo_outs }
} else {
debug_assert_eq!(rct_type, RctType::MlsagBulletproofsCompactAmount);
RctPrunable::MlsagBulletproofsCompactAmount { bulletproof, mlsags, pseudo_outs }
}
}
RctType::ClsagBulletproof | RctType::ClsagBulletproofPlus => RctPrunable::Clsag {
bulletproof: {
if read_varint::<_, u64>(r)? != 1 {
Err(io::Error::other("n bulletproofs instead of one"))?;
}
(if rct_type == RctType::ClsagBulletproof {
Bulletproof::read
} else {
Bulletproof::read_plus
})(r)?
},
clsags: (0 .. inputs).map(|_| Clsag::read(ring_length, r)).collect::<Result<_, _>>()?,
pseudo_outs: read_raw_vec(read_point, inputs, r)?,
},
})
}
/// Write the RctPrunable as necessary for signing the signature.
pub(crate) fn signature_write<W: Write>(&self, w: &mut W) -> io::Result<()> {
match self {
RctPrunable::AggregateMlsagBorromean { borromean, .. } |
RctPrunable::MlsagBorromean { borromean, .. } => {
borromean.iter().try_for_each(|rs| rs.write(w))
}
RctPrunable::MlsagBulletproofs { bulletproof, .. } |
RctPrunable::MlsagBulletproofsCompactAmount { bulletproof, .. } |
RctPrunable::Clsag { bulletproof, .. } => bulletproof.signature_write(w),
}
}
}
/// The RingCT proofs.
///
/// This contains both the RctBase and RctPrunable structs.
///
/// The C++ codebase refers to this as rct_signatures.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct RctProofs {
/// The data necessary for handling this transaction.
pub base: RctBase,
/// The data necessary for verifying this transaction.
pub prunable: RctPrunable,
}
impl RctProofs {
/// RctType for a given RctProofs struct.
pub fn rct_type(&self) -> RctType {
match &self.prunable {
RctPrunable::AggregateMlsagBorromean { .. } => RctType::AggregateMlsagBorromean,
RctPrunable::MlsagBorromean { .. } => RctType::MlsagBorromean,
RctPrunable::MlsagBulletproofs { .. } => RctType::MlsagBulletproofs,
RctPrunable::MlsagBulletproofsCompactAmount { .. } => RctType::MlsagBulletproofsCompactAmount,
RctPrunable::Clsag { bulletproof, .. } => {
if matches!(bulletproof, Bulletproof::Original { .. }) {
RctType::ClsagBulletproof
} else {
RctType::ClsagBulletproofPlus
}
}
}
}
/// Write the RctProofs.
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
let rct_type = self.rct_type();
self.base.write(w, rct_type)?;
self.prunable.write(w, rct_type)
}
/// Serialize the RctProofs to a `Vec<u8>`.
pub fn serialize(&self) -> Vec<u8> {
let mut serialized = vec![];
self.write(&mut serialized).unwrap();
serialized
}
/// Read a RctProofs.
pub fn read<R: Read>(
ring_length: usize,
inputs: usize,
outputs: usize,
r: &mut R,
) -> io::Result<Option<RctProofs>> {
let Some((rct_type, base)) = RctBase::read(inputs, outputs, r)? else { return Ok(None) };
Ok(Some(RctProofs {
base,
prunable: RctPrunable::read(rct_type, ring_length, inputs, outputs, r)?,
}))
}
}
/// A pruned set of RingCT proofs.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct PrunedRctProofs {
/// The type of RctProofs this used to be.
pub rct_type: RctType,
/// The data necessary for handling this transaction.
pub base: RctBase,
}

View File

@@ -0,0 +1 @@
mod transaction;

View File

@@ -0,0 +1,287 @@
use curve25519_dalek::{
edwards::{CompressedEdwardsY, EdwardsPoint},
scalar::Scalar,
};
use serde_json::Value;
use crate::{
ringct::RctPrunable,
transaction::{NotPruned, Transaction, Timelock, Input},
};
const TRANSACTIONS: &str = include_str!("./vectors/transactions.json");
const CLSAG_TX: &str = include_str!("./vectors/clsag_tx.json");
const RING_DATA: &str = include_str!("./vectors/ring_data.json");
#[derive(serde::Deserialize)]
struct Vector {
id: String,
hex: String,
signature_hash: String,
tx: Value,
}
fn tx_vectors() -> Vec<Vector> {
serde_json::from_str(TRANSACTIONS).unwrap()
}
fn point(hex: &Value) -> EdwardsPoint {
CompressedEdwardsY(hex::decode(hex.as_str().unwrap()).unwrap().try_into().unwrap())
.decompress()
.unwrap()
}
fn scalar(hex: &Value) -> Scalar {
Scalar::from_canonical_bytes(hex::decode(hex.as_str().unwrap()).unwrap().try_into().unwrap())
.unwrap()
}
fn point_vector(val: &Value) -> Vec<EdwardsPoint> {
let mut v = vec![];
for hex in val.as_array().unwrap() {
v.push(point(hex));
}
v
}
fn scalar_vector(val: &Value) -> Vec<Scalar> {
let mut v = vec![];
for hex in val.as_array().unwrap() {
v.push(scalar(hex));
}
v
}
#[test]
fn parse() {
for v in tx_vectors() {
let tx =
Transaction::<NotPruned>::read(&mut hex::decode(v.hex.clone()).unwrap().as_slice()).unwrap();
// check version
assert_eq!(tx.version(), v.tx["version"]);
// check unlock time
match tx.prefix().additional_timelock {
Timelock::None => assert_eq!(0, v.tx["unlock_time"]),
Timelock::Block(h) => assert_eq!(h, v.tx["unlock_time"]),
Timelock::Time(t) => assert_eq!(t, v.tx["unlock_time"]),
}
// check inputs
let inputs = v.tx["vin"].as_array().unwrap();
assert_eq!(tx.prefix().inputs.len(), inputs.len());
for (i, input) in tx.prefix().inputs.iter().enumerate() {
match input {
Input::Gen(h) => assert_eq!(*h, inputs[i]["gen"]["height"]),
Input::ToKey { amount, key_offsets, key_image } => {
let key = &inputs[i]["key"];
assert_eq!(amount.unwrap_or(0), key["amount"]);
assert_eq!(*key_image, point(&key["k_image"]));
assert_eq!(key_offsets, key["key_offsets"].as_array().unwrap());
}
}
}
// check outputs
let outputs = v.tx["vout"].as_array().unwrap();
assert_eq!(tx.prefix().outputs.len(), outputs.len());
for (i, output) in tx.prefix().outputs.iter().enumerate() {
assert_eq!(output.amount.unwrap_or(0), outputs[i]["amount"]);
if output.view_tag.is_some() {
assert_eq!(output.key, point(&outputs[i]["target"]["tagged_key"]["key"]).compress());
let view_tag =
hex::decode(outputs[i]["target"]["tagged_key"]["view_tag"].as_str().unwrap()).unwrap();
assert_eq!(view_tag.len(), 1);
assert_eq!(output.view_tag.unwrap(), view_tag[0]);
} else {
assert_eq!(output.key, point(&outputs[i]["target"]["key"]).compress());
}
}
// check extra
assert_eq!(tx.prefix().extra, v.tx["extra"].as_array().unwrap().as_slice());
match &tx {
Transaction::V1 { signatures, .. } => {
// check signatures for v1 txs
let sigs_array = v.tx["signatures"].as_array().unwrap();
for (i, sig) in signatures.iter().enumerate() {
let tx_sig = hex::decode(sigs_array[i].as_str().unwrap()).unwrap();
for (i, sig) in sig.sigs.iter().enumerate() {
let start = i * 64;
let c: [u8; 32] = tx_sig[start .. (start + 32)].try_into().unwrap();
let s: [u8; 32] = tx_sig[(start + 32) .. (start + 64)].try_into().unwrap();
assert_eq!(sig.c, Scalar::from_canonical_bytes(c).unwrap());
assert_eq!(sig.s, Scalar::from_canonical_bytes(s).unwrap());
}
}
}
Transaction::V2 { proofs: None, .. } => assert_eq!(v.tx["rct_signatures"]["type"], 0),
Transaction::V2 { proofs: Some(proofs), .. } => {
// check rct signatures
let rct = &v.tx["rct_signatures"];
assert_eq!(u8::from(proofs.rct_type()), rct["type"]);
assert_eq!(proofs.base.fee, rct["txnFee"]);
assert_eq!(proofs.base.commitments, point_vector(&rct["outPk"]));
let ecdh_info = rct["ecdhInfo"].as_array().unwrap();
assert_eq!(proofs.base.encrypted_amounts.len(), ecdh_info.len());
for (i, ecdh) in proofs.base.encrypted_amounts.iter().enumerate() {
let mut buf = vec![];
ecdh.write(&mut buf).unwrap();
assert_eq!(buf, hex::decode(ecdh_info[i]["amount"].as_str().unwrap()).unwrap());
}
// check ringct prunable
match &proofs.prunable {
RctPrunable::Clsag { bulletproof: _, clsags, pseudo_outs } => {
// check bulletproofs
/* TODO
for (i, bp) in bulletproofs.iter().enumerate() {
match bp {
Bulletproof::Original(o) => {
let bps = v.tx["rctsig_prunable"]["bp"].as_array().unwrap();
assert_eq!(bulletproofs.len(), bps.len());
assert_eq!(o.A, point(&bps[i]["A"]));
assert_eq!(o.S, point(&bps[i]["S"]));
assert_eq!(o.T1, point(&bps[i]["T1"]));
assert_eq!(o.T2, point(&bps[i]["T2"]));
assert_eq!(o.taux, scalar(&bps[i]["taux"]));
assert_eq!(o.mu, scalar(&bps[i]["mu"]));
assert_eq!(o.L, point_vector(&bps[i]["L"]));
assert_eq!(o.R, point_vector(&bps[i]["R"]));
assert_eq!(o.a, scalar(&bps[i]["a"]));
assert_eq!(o.b, scalar(&bps[i]["b"]));
assert_eq!(o.t, scalar(&bps[i]["t"]));
}
Bulletproof::Plus(p) => {
let bps = v.tx["rctsig_prunable"]["bpp"].as_array().unwrap();
assert_eq!(bulletproofs.len(), bps.len());
assert_eq!(p.A, point(&bps[i]["A"]));
assert_eq!(p.A1, point(&bps[i]["A1"]));
assert_eq!(p.B, point(&bps[i]["B"]));
assert_eq!(p.r1, scalar(&bps[i]["r1"]));
assert_eq!(p.s1, scalar(&bps[i]["s1"]));
assert_eq!(p.d1, scalar(&bps[i]["d1"]));
assert_eq!(p.L, point_vector(&bps[i]["L"]));
assert_eq!(p.R, point_vector(&bps[i]["R"]));
}
}
}
*/
// check clsags
let cls = v.tx["rctsig_prunable"]["CLSAGs"].as_array().unwrap();
for (i, cl) in clsags.iter().enumerate() {
assert_eq!(cl.D, point(&cls[i]["D"]));
assert_eq!(cl.c1, scalar(&cls[i]["c1"]));
assert_eq!(cl.s, scalar_vector(&cls[i]["s"]));
}
// check pseudo outs
assert_eq!(pseudo_outs, &point_vector(&v.tx["rctsig_prunable"]["pseudoOuts"]));
}
// TODO: Add
_ => panic!("non-null/CLSAG test vector"),
}
}
}
// check serialized hex
let mut buf = Vec::new();
tx.write(&mut buf).unwrap();
let serialized_tx = hex::encode(&buf);
assert_eq!(serialized_tx, v.hex);
}
}
#[test]
fn signature_hash() {
for v in tx_vectors() {
let tx = Transaction::read(&mut hex::decode(v.hex.clone()).unwrap().as_slice()).unwrap();
// check for signature hashes
if let Some(sig_hash) = tx.signature_hash() {
assert_eq!(sig_hash, hex::decode(v.signature_hash.clone()).unwrap().as_slice());
} else {
// make sure it is a miner tx.
assert!(matches!(tx.prefix().inputs[0], Input::Gen(_)));
}
}
}
#[test]
fn hash() {
for v in &tx_vectors() {
let tx = Transaction::read(&mut hex::decode(v.hex.clone()).unwrap().as_slice()).unwrap();
assert_eq!(tx.hash(), hex::decode(v.id.clone()).unwrap().as_slice());
}
}
#[test]
fn clsag() {
/*
// following keys belong to the wallet that created the CLSAG_TX, and to the
// CLSAG_TX itself and here for debug purposes in case this test unexpectedly fails some day.
let view_key = "9df81dd2e369004d3737850e4f0abaf2111720f270b174acf8e08547e41afb0b";
let spend_key = "25f7339ce03a0206129c0bdd78396f80bf28183ccd16084d4ab1cbaf74f0c204";
let tx_key = "650c8038e5c6f1c533cacc1713ac27ef3ec70d7feedde0c5b37556d915b4460c";
*/
#[derive(serde::Deserialize)]
struct TxData {
hex: String,
tx: Value,
}
#[derive(serde::Deserialize)]
struct OutData {
key: Value,
mask: Value,
}
let tx_data = serde_json::from_str::<TxData>(CLSAG_TX).unwrap();
let out_data = serde_json::from_str::<Vec<Vec<OutData>>>(RING_DATA).unwrap();
let tx =
Transaction::<NotPruned>::read(&mut hex::decode(tx_data.hex).unwrap().as_slice()).unwrap();
// gather rings
let mut rings = vec![];
for data in out_data {
let mut ring = vec![];
for out in &data {
ring.push([point(&out.key), point(&out.mask)]);
}
rings.push(ring)
}
// gather key images
let mut key_images = vec![];
let inputs = tx_data.tx["vin"].as_array().unwrap();
for input in inputs {
key_images.push(point(&input["key"]["k_image"]));
}
// gather pseudo_outs
let mut pseudo_outs = vec![];
let pouts = tx_data.tx["rctsig_prunable"]["pseudoOuts"].as_array().unwrap();
for po in pouts {
pseudo_outs.push(point(po));
}
// verify clsags
match tx {
Transaction::V2 { proofs: Some(ref proofs), .. } => match &proofs.prunable {
RctPrunable::Clsag { bulletproof: _, clsags, .. } => {
for (i, cls) in clsags.iter().enumerate() {
cls
.verify(&rings[i], &key_images[i], &pseudo_outs[i], &tx.signature_hash().unwrap())
.unwrap();
}
}
// TODO: Add
_ => panic!("non-CLSAG test vector"),
},
// TODO: Add
_ => panic!("non-CLSAG test vector"),
}
}

View File

@@ -0,0 +1,78 @@
{
"hex": "020002020010020102010101010302010c0201060103d8c6f077bb201ffdc16407df206cb5962ec635a4a4c9cd7551b88698d1bef497020010000402040801010303030101020104018267c18a435f4a5dea50ad0f10755a4fd7783340beb3a3903a67fa14938edf420200039716cdbae38def9a74e7df5402c108270a1d5fc87c7e5ebaaaed68aae77701e3cf0003082e27ca8af2b9e3004156c152aa98503b548b1591fdcd839ab550612ae6c9dc7e2c01a57c93fb0ca77ab96b7dfd7380c4842d1e58c055430e0d425cd1c76c578cca390209019519f8c1ce5e20300680e5a0da09acd081c0dd2c7178a341382720ada87588a96ac5cff1623fd2e4aaf56ed395a325393fbd950428a3ff7e6dc6c559669c8d5e8fb80d5e979c8a81c89754201d4bd094c37c143759260e282555dfed3100013256ca0156c1c34dc569565039c27f784b45ec50ba816f69b54ae3df98d841070f51aec2a8afd4991d5bbf50b785d0bdc2a6491c5ab45795d7ce3b08d63282907c52f9951e711cb6a2cba1aba1f7849a669345711263cc736e2d4e1c7308c5e7cb97e948ed647f89fc9869fb9c9a5a742e5e7be419cce7a5e99a5b21cb491f00003ec1da7e8cec39b709d46fab65f59f5f6147c1e4429d18d8bf6e3e62639102a300ce6006a20403ef021a197b6c632ac280e674c7aad08290424271dec4de010710ee7895389150dd15017cfd5f47ea9dddd11e218251433906f62aff6b8cb2b5f8cca25add297da40d7cddbea718703ff9ad3795fcdc172a34c73179326c16f5274de69073281f3276d800fe7fbd01a07d14a42ce367c32727a9f0bc8c8d6ab4b3b17dd981bdc522595fc1bfe83ad3976876fb3bb2e4bd4392ac1a94ac22cbcc326ede82d1af2f1ec9d4ac596b22d035c7f1ac11d8ace7c5a70b30e39596ded794077ae55144e3f4b0c17cbc4f5a960129eb5321077bb7e2b9e4621e17fbcf2960abae1e1a9f89af21cc2fcce410a839186b8da92966415d6dd3ad772d652cbe075af46b97ae7062ccbaa328e371a351492f6860832c5bfdd7b77e8611b7441ecfa0967e66c13cb9ab348bf78a15bbd2d9bec6b8ec5cdd5f84a91580758247da84afed22ec2cf89d632e406fdc927e48ebfaacd0a0b715a968c9cfc74fff611f4cda4b6cb9eb1e044a71c58a832c5ae7551833c0ba2ab6f9d1e466e5757c230157cd3099686bf89e8f9eb822ea702e13e38f669603dd3c7c8be90daf192de689ab2078d16cf489f3782e70469fbe01f918297e0db6cef3bf48e0293b6856d348fda3a2d76bf899432acef74aa42961be28635d1899509b9d368bc42a18e08d2b94b055da149139c347f7c0b2a381dfaa12aaaabe076f38fe12372d1ba17cd0d808ed5b4b911f8cee2e45841a4c879f40968e455ba5a796b27c968be0f7e88daf0b766fcf2c5986fbe14b2e0433cecb04af100ec81d03e2875d25483d0a9dc9dc0a42150a64e894af1655e9ab99f629826f63c01e44b366c5fe2959c7396450360a3156ad081764b5904a7654fe82a2b1d52db46361c0b08dfeee383165641e6e0e5733e5fb99fc8c75ba5cf230518b1e384d4441251840e810aed950eb27899809711d42c54f8fc0647537e249e510738412c399b915ff923e9209cdd12820720b8b07086f3361d6b95934f994a8ac4fb6a9598f11d54bbbcfc33e71b9f73570012b3520914dfab3f3fe15abad981d8ed71dab71ac8f45f187f62ad440a83d000e08fc039ece25e7eadd0ce169ccda8182321cd73eba6f6d0e4f482a061eb4190fe4051e6988a47165cb2cf39973b1a555cc92d662f4e856a91c0cd51a486b960cfc850c4fc854f9a4aade4336942cb50cb50ae3bc31d3da50b719196d5fd40f02b1addad16de443e825bf7177beaac79adc6b198115f408a391a94a8517b7e50fd57663df52309c0a00b0b61373f895206771be8b185c54da6f805b561264aa019ef3bd1dcded26fc45a6a0e39cbb7bc6a7025ab858bc8e54a99da3aedce68f00bacc83a7eb3553ac626881188329b6ba86a53aaaaed9bd9efb0528f08c649c093f005dd0fa9620b0a40fc3f248c1d0edb8f70ff05c7254de0f8faab8315443021b3d279f5a4218c3126dee5d6eceae1c49eabdd04d8a0cdb6814c422b3ea69b3be3794f42081e65dc47b1d2fc2f5705cef816596416c373bd60abc4ff06b3f02ef34dc290f987607bdb16c1650307ea3bc0fc7a62ce86e7129293d7530c3cf09dc731e22c18daec3c639575421b079fa57be56693278125b2aa50c299ac4f8020714c6ac666b7fb7471c63adda93f1fa6733729f7b6e326ac04744f9c3223d0456ee515d0bfe27101f907cca958dddb90717bed5229c1a02928fab9e7be4e4012c96d3acda0ebca72e63f41efdad5c9baa19bffd1216e4c3e2e5564e823b57054a3a2cf2c3318f214d23f24304655e73d5001518633757f6cbe6711f2a5f2601df20a753caaa87a32fe627b6ce7573ce77957c7b6401959824fd49bc7063670fb18fcc1f2de113affd868eb76c7fbe12997024dc493b6a26563a80574a52760a7b384fd2f9d23d8dfe4d226b15086751d4f383d4bca7cf080fd471b8a218b709b539f4e5417677f43627ef06b70c24edacce80bdd10ca2ac9af8aa3f6453cc08da75ee99409447225843c143fca551167a4aa5fd2354a5420c35c0006731950d6c356218d8cf365e084d9bb52c793322aa2d8d05c4164d9ffe81ce09e4f17802efa7461d375a5cff4c17ab0cdc5767a8f7d34091921fd4620660470ea9305f00dd9e6ee5ca4054ac0b36d4e2b58006224559cc19a3a4e48f66aa596295541007f2524b2198f3c0c688fbbc38590f59674b25e528ac2115a0f7da805d9c5810065f95c7c7ece23d2de922e55a77f967baab6d9db543e49734a8c4bc23c5ae640edb904851b4856c5a1ce4729957f4d000e70cb88c56d80bf6e693a5c67d5661911374d7aa7f6e6f4a5b340a9954d9cf8bd5d2f4b4a37f946e15bca800978ae745eec2096b3def10f9703a6e2040df0d8a89bf1562bb29d3a13df2f9a77c3e064e",
"tx": {
"version": 2,
"unlock_time": 0,
"vin": [ {
"key": {
"amount": 0,
"key_offsets": [ 2, 1, 2, 1, 1, 1, 1, 3, 2, 1, 12, 2, 1, 6, 1, 3],
"k_image": "d8c6f077bb201ffdc16407df206cb5962ec635a4a4c9cd7551b88698d1bef497"
}
}, {
"key": {
"amount": 0,
"key_offsets": [ 0, 4, 2, 4, 8, 1, 1, 3, 3, 3, 1, 1, 2, 1, 4, 1
],
"k_image": "8267c18a435f4a5dea50ad0f10755a4fd7783340beb3a3903a67fa14938edf42"
}
}
],
"vout": [ {
"amount": 0,
"target": {
"tagged_key": {
"key": "9716cdbae38def9a74e7df5402c108270a1d5fc87c7e5ebaaaed68aae77701e3",
"view_tag": "cf"
}
}
}, {
"amount": 0,
"target": {
"tagged_key": {
"key": "082e27ca8af2b9e3004156c152aa98503b548b1591fdcd839ab550612ae6c9dc",
"view_tag": "7e"
}
}
}
],
"extra": [ 1, 165, 124, 147, 251, 12, 167, 122, 185, 107, 125, 253, 115, 128, 196, 132, 45, 30, 88, 192, 85, 67, 14, 13, 66, 92, 209, 199, 108, 87, 140, 202, 57, 2, 9, 1, 149, 25, 248, 193, 206, 94, 32, 48
],
"rct_signatures": {
"type": 6,
"txnFee": 2605200000,
"ecdhInfo": [ {
"amount": "acd081c0dd2c7178"
}, {
"amount": "a341382720ada875"
}],
"outPk": [ "88a96ac5cff1623fd2e4aaf56ed395a325393fbd950428a3ff7e6dc6c559669c", "8d5e8fb80d5e979c8a81c89754201d4bd094c37c143759260e282555dfed3100"]
},
"rctsig_prunable": {
"nbp": 1,
"bpp": [ {
"A": "3256ca0156c1c34dc569565039c27f784b45ec50ba816f69b54ae3df98d84107",
"A1": "0f51aec2a8afd4991d5bbf50b785d0bdc2a6491c5ab45795d7ce3b08d6328290",
"B": "7c52f9951e711cb6a2cba1aba1f7849a669345711263cc736e2d4e1c7308c5e7",
"r1": "cb97e948ed647f89fc9869fb9c9a5a742e5e7be419cce7a5e99a5b21cb491f00",
"s1": "003ec1da7e8cec39b709d46fab65f59f5f6147c1e4429d18d8bf6e3e62639102",
"d1": "a300ce6006a20403ef021a197b6c632ac280e674c7aad08290424271dec4de01",
"L": [ "10ee7895389150dd15017cfd5f47ea9dddd11e218251433906f62aff6b8cb2b5", "f8cca25add297da40d7cddbea718703ff9ad3795fcdc172a34c73179326c16f5", "274de69073281f3276d800fe7fbd01a07d14a42ce367c32727a9f0bc8c8d6ab4", "b3b17dd981bdc522595fc1bfe83ad3976876fb3bb2e4bd4392ac1a94ac22cbcc", "326ede82d1af2f1ec9d4ac596b22d035c7f1ac11d8ace7c5a70b30e39596ded7", "94077ae55144e3f4b0c17cbc4f5a960129eb5321077bb7e2b9e4621e17fbcf29", "60abae1e1a9f89af21cc2fcce410a839186b8da92966415d6dd3ad772d652cbe"
],
"R": [ "5af46b97ae7062ccbaa328e371a351492f6860832c5bfdd7b77e8611b7441ecf", "a0967e66c13cb9ab348bf78a15bbd2d9bec6b8ec5cdd5f84a91580758247da84", "afed22ec2cf89d632e406fdc927e48ebfaacd0a0b715a968c9cfc74fff611f4c", "da4b6cb9eb1e044a71c58a832c5ae7551833c0ba2ab6f9d1e466e5757c230157", "cd3099686bf89e8f9eb822ea702e13e38f669603dd3c7c8be90daf192de689ab", "2078d16cf489f3782e70469fbe01f918297e0db6cef3bf48e0293b6856d348fd", "a3a2d76bf899432acef74aa42961be28635d1899509b9d368bc42a18e08d2b94"
]
}
],
"CLSAGs": [ {
"s": [ "b055da149139c347f7c0b2a381dfaa12aaaabe076f38fe12372d1ba17cd0d808", "ed5b4b911f8cee2e45841a4c879f40968e455ba5a796b27c968be0f7e88daf0b", "766fcf2c5986fbe14b2e0433cecb04af100ec81d03e2875d25483d0a9dc9dc0a", "42150a64e894af1655e9ab99f629826f63c01e44b366c5fe2959c7396450360a", "3156ad081764b5904a7654fe82a2b1d52db46361c0b08dfeee383165641e6e0e", "5733e5fb99fc8c75ba5cf230518b1e384d4441251840e810aed950eb27899809", "711d42c54f8fc0647537e249e510738412c399b915ff923e9209cdd12820720b", "8b07086f3361d6b95934f994a8ac4fb6a9598f11d54bbbcfc33e71b9f7357001", "2b3520914dfab3f3fe15abad981d8ed71dab71ac8f45f187f62ad440a83d000e", "08fc039ece25e7eadd0ce169ccda8182321cd73eba6f6d0e4f482a061eb4190f", "e4051e6988a47165cb2cf39973b1a555cc92d662f4e856a91c0cd51a486b960c", "fc850c4fc854f9a4aade4336942cb50cb50ae3bc31d3da50b719196d5fd40f02", "b1addad16de443e825bf7177beaac79adc6b198115f408a391a94a8517b7e50f", "d57663df52309c0a00b0b61373f895206771be8b185c54da6f805b561264aa01", "9ef3bd1dcded26fc45a6a0e39cbb7bc6a7025ab858bc8e54a99da3aedce68f00", "bacc83a7eb3553ac626881188329b6ba86a53aaaaed9bd9efb0528f08c649c09"],
"c1": "3f005dd0fa9620b0a40fc3f248c1d0edb8f70ff05c7254de0f8faab831544302",
"D": "1b3d279f5a4218c3126dee5d6eceae1c49eabdd04d8a0cdb6814c422b3ea69b3"
}, {
"s": [ "be3794f42081e65dc47b1d2fc2f5705cef816596416c373bd60abc4ff06b3f02", "ef34dc290f987607bdb16c1650307ea3bc0fc7a62ce86e7129293d7530c3cf09", "dc731e22c18daec3c639575421b079fa57be56693278125b2aa50c299ac4f802", "0714c6ac666b7fb7471c63adda93f1fa6733729f7b6e326ac04744f9c3223d04", "56ee515d0bfe27101f907cca958dddb90717bed5229c1a02928fab9e7be4e401", "2c96d3acda0ebca72e63f41efdad5c9baa19bffd1216e4c3e2e5564e823b5705", "4a3a2cf2c3318f214d23f24304655e73d5001518633757f6cbe6711f2a5f2601", "df20a753caaa87a32fe627b6ce7573ce77957c7b6401959824fd49bc7063670f", "b18fcc1f2de113affd868eb76c7fbe12997024dc493b6a26563a80574a52760a", "7b384fd2f9d23d8dfe4d226b15086751d4f383d4bca7cf080fd471b8a218b709", "b539f4e5417677f43627ef06b70c24edacce80bdd10ca2ac9af8aa3f6453cc08", "da75ee99409447225843c143fca551167a4aa5fd2354a5420c35c0006731950d", "6c356218d8cf365e084d9bb52c793322aa2d8d05c4164d9ffe81ce09e4f17802", "efa7461d375a5cff4c17ab0cdc5767a8f7d34091921fd4620660470ea9305f00", "dd9e6ee5ca4054ac0b36d4e2b58006224559cc19a3a4e48f66aa596295541007", "f2524b2198f3c0c688fbbc38590f59674b25e528ac2115a0f7da805d9c581006"],
"c1": "5f95c7c7ece23d2de922e55a77f967baab6d9db543e49734a8c4bc23c5ae640e",
"D": "db904851b4856c5a1ce4729957f4d000e70cb88c56d80bf6e693a5c67d566191"
}],
"pseudoOuts": [ "1374d7aa7f6e6f4a5b340a9954d9cf8bd5d2f4b4a37f946e15bca800978ae745", "eec2096b3def10f9703a6e2040df0d8a89bf1562bb29d3a13df2f9a77c3e064e"]
}
}
}

View File

@@ -0,0 +1,134 @@
[
[
{
"key": "a1abc026eb4a18ca197ca7dbd32f7a4e66cda075a7c07ee6cbe68639a4b4ee46",
"mask": "48d7f0b8796720c7edef5e3797135b3e5ad2ae23db1d934bcf6d6bc396b8ed47"
},
{
"key": "a374121e22ed620248c970e7f32ea7598b054f73c1edec33c4e1b18a73c35c14",
"mask": "15beeeedc9b33615097e0fac0acc6a0984e139fa2b4196896877a8cc3ebc3590"
},
{
"key": "e2ac4d36f9567092563a09c7a19c5e21c39598f5d9d9dd8733b61cebb3ea8662",
"mask": "3d9105f85f9edd3f7f72b62385bb9a42d549331d3babea6cf73bbbcde8e4f53c"
},
{
"key": "68c08bbbfdb3ad736dfed5854264a3b410de40d8f3d02b22f5cf75f69f6e2e1f",
"mask": "36c39958ddcad401d85d63883da510505650321ad7a26859e8b1b6c28204d274"
},
{
"key": "7b8b580f7a2288040a0755810c5708c5a8277d139762545082785260275678e4",
"mask": "498105ec1dc7559becfb833140c5049382b846eff812616a2414494d7a46930d"
},
{
"key": "348d9be3f2b42686c2a919ba1515c5a540c5ffb4c1762e4a371b42643ff69b3b",
"mask": "eeca9ed04ba72a89dbd85564cf3084daad577634db09d048895524f1ded26b19"
},
{
"key": "91a59666453bcc55d2a02480dfe2029082e24548cdfd7d614be31657fdd75357",
"mask": "ae7f14cbb31d24b727d8680fbd03bcc177fc67b982edeca54e6b2b47d6b8d012"
},
{
"key": "9868cb5201d4b00e5a3552a7f485662dfb3ca74b79f6bd069ee0a4650597abbc",
"mask": "570e3b126e429022177d22fd09d73c6950676c82a4872addb3afa950646c5f1d"
},
{
"key": "56d05fced0eb9dda981a26fdd4170f46de2b0a35c70f02ceae23ad9f2ed8a5b0",
"mask": "a0e20ecd8526bd2a640c4df42c187fcf75d05660ba61262c93b19384b8fad49b"
},
{
"key": "9e82f65349da1e0dacf5d96a9c0f80c0c5fd0fc2437cafbcc38b2f20e721abc5",
"mask": "e83344061c0632631eec627bb2103898cfc230b35e0177681e48f0ee4b6d37c8"
},
{
"key": "2590a255607ab619fcd62142f4b002818f2d55dbb5b8665500854203b83e5c86",
"mask": "e9c103485b3f4dadab560e8efc67c594ba11f16513685f0faff78c6fdf4de061"
},
{
"key": "c0e22332d897f0637440ad151089652e59dcbf27dc84b11c2efbe686a9e7afb5",
"mask": "363d5dcbc765854e830dc52762e24f71d7c85f6095227551f3ef6ada6aa25964"
},
{
"key": "360e4efb484e8d419bdda5f581703de716671e3516d1c9deb97204f9b4c9c0d4",
"mask": "29ef141fa24ef86af35af48094928392543a9e7e7726ae92a9da322178e680ad"
},
{
"key": "5bb515d131f03bbb3be4e710b83589f62f07f185b9ad344095df47092f41b8e0",
"mask": "94fd6083b669533eebfa49a1cb47b94555e8be7d5f84573354b0201229d07bed"
},
{
"key": "5ce647c3017ec3c36a2385e2b11fb9a452a5766987d80531bec75952924ed896",
"mask": "8f61d7be3b4f2252810fbade3bbac970ccff55c453e34405836545f3e49be6f5"
},
{
"key": "dbc787f7ca41996a981a0ebb498a8d565dfa62a3b3b169c4c3018fff2233a757",
"mask": "9bb749be705747d9c28168c0446d589b3ac18949fa0087e230805aaff5a9982f"
}
],
[
{
"key": "d10621b38fbc5237061b2d3503866f0be46aaa0694c9f9d747f7ed19acebe8ef",
"mask": "a1a7a42155f0abff0353a6008eda2a9b16d9ffcf7584a38933cce3e3976987cd"
},
{
"key": "a9afb71ae2db057049131df856d246f7088a656cc85297ce7e1ef339bd6e0c96",
"mask": "96e9dc7a96a19c9ebaeb33ab94e7e9d86d88df1c1b11006b297b74f529f37f5a"
},
{
"key": "68c08bbbfdb3ad736dfed5854264a3b410de40d8f3d02b22f5cf75f69f6e2e1f",
"mask": "36c39958ddcad401d85d63883da510505650321ad7a26859e8b1b6c28204d274"
},
{
"key": "74193737897162c8b2c380ff34674e3bfbfb2ac7e1c7aacbb13f2a3a8fb2b043",
"mask": "8157e47f9998f4afdce72a328eb9e897a57a5819b838ed1b517ea2c938e0c94f"
},
{
"key": "96e002055aafbfdd1136cc587543e5c0e51da0d9682879c107abab3cdcdb9479",
"mask": "f76929f6dba6d75bec713a02677aa7ad39dd4319077bfa7189fe65fe86b2ee9a"
},
{
"key": "2a72f3b2cb3e10727fbfc09d2c726763000a92f77f2f000c63dee714a6c7424d",
"mask": "db459ca84da12ebab294b31961838c43cee1868f0690d143c93da1f2f825d07f"
},
{
"key": "797f5f3a30ce8d4b19305ca9d8193033d649f0a74705203da9f3f106ad60dfb4",
"mask": "39339ac52a1194790b1bb5db0b119d403a1d5dcc4db4f8819fca4d425d5b2614"
},
{
"key": "b0c42947607815eba320f97e7c9ecd092fe187fb67d7263540015e6308f6dc1a",
"mask": "6b92c8c269319192298307feb26a7b64fb78d877ac2e49a594650227f26e64bc"
},
{
"key": "59015cfd533a742857454dce9d82846fce08ab7d96c5583640cf6e38ecf0445e",
"mask": "cf375f037e253ab6f52699fbba73f796ee2140e546710a1faa3c9f09b4f570ac"
},
{
"key": "c0e22332d897f0637440ad151089652e59dcbf27dc84b11c2efbe686a9e7afb5",
"mask": "363d5dcbc765854e830dc52762e24f71d7c85f6095227551f3ef6ada6aa25964"
},
{
"key": "360e4efb484e8d419bdda5f581703de716671e3516d1c9deb97204f9b4c9c0d4",
"mask": "29ef141fa24ef86af35af48094928392543a9e7e7726ae92a9da322178e680ad"
},
{
"key": "92619df80e988c0b2dfb63dd6324ff2979ca319bf8200260b28944753dda4ac1",
"mask": "0a574b0aca86da38dd7aeb58d92550dc558c680deaa63c69e31e9a78e88a3559"
},
{
"key": "0ac7e630a04be92b1f3c821c50ec80a2813f7bee4c1ab117967bc26263d4fd84",
"mask": "ed0bd4d707ab3deaf18437ae9d945da2d3f2c6e758068ce57972d676da2a24bf"
},
{
"key": "b97300cdb6ef63a6990686521138b5c7c80cf6c9a8844518352f3ef1130d413d",
"mask": "690c312586bbdf123d9e34ad7955e1c2ae5259cd3effd0b08b19cb556d65ec25"
},
{
"key": "1a62237b77e28713e5a47129f1ba18be27a5139d6f1e6d6d38c78705143b3ea5",
"mask": "39f6ba6d816695f20212042b1048301cd637161f685d7c2b61379b907b7b4c59"
},
{
"key": "ffca492152d8206bb7f215d2408669856203edffd424f4fc6a0304def2195717",
"mask": "cd7684b7c32531b363784d86bee71731c113c545c67103ec1265c362de7e5555"
}
]
]

View File

@@ -0,0 +1,324 @@
[
{
"id": "373a2ace627debaf8bfd493155fd3c00c5c2fc164400ec22e79ee79a1ac487c4",
"hex": "02f78dae0101ffbb8dae0101e0b2d2b9c21103e6854544fbb66d55fc3546f4d3e69f8234257b69fa2237712af3b058a5f01ba14a340173f263b8a4bbc46dfb6f29e0584adbfffdf7a47c929d77c2d0c142afea2b05300211000000f7eeeb3f0e00000000000000000000",
"signature_hash": "",
"tx": {
"version": 2,
"unlock_time": 2852599,
"vin": [
{
"gen": {
"height": 2852539
}
}
],
"vout": [
{
"amount": 601953180000,
"target": {
"tagged_key": {
"key": "e6854544fbb66d55fc3546f4d3e69f8234257b69fa2237712af3b058a5f01ba1",
"view_tag": "4a"
}
}
}
],
"extra": [ 1, 115, 242, 99, 184, 164, 187, 196, 109, 251, 111, 41, 224, 88, 74, 219, 255, 253, 247, 164, 124, 146, 157, 119, 194, 208, 193, 66, 175, 234, 43, 5, 48, 2, 17, 0, 0, 0, 247, 238, 235, 63, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
"rct_signatures": {
"type": 0
}
}
},
{
"id": "c39652b79beb888464525fee06c3d078463af5b76d493785f8903cae93405603",
"hex": "02000102000be9aac314d8e710844d8d258133d9f701b0649e568b0cb50b1dea8103138a37c5543f3c632ef80331940cabeba29b758045db328d8d8a99de380200025155f659da61b507b0b8591cbef0ba1534b9db29a69be4a933f7897e58870b250002002de4643160fb8351a841f8079aa5af2ac62c9631bb2d5a48fcdb564cb699d12c01eaaa5acba0bc44657da783903d3de7febf7124ae03cf578938244068b475d906020901da0190b93466979e0580efcf037eafd43a457b940e592f261bd165059a9d3b92aa3baaed18346157d1361e30c553df17940985b375fc89b9c09d613d5daf007ed1ce05128cfc4d37181a5f2d6f56690357032dbcc6e85424e5ad79338801df166e5b93e427f8b86079e0de0695d24a4ac5098d98876da5f892359c982015fc0461847fd2955f6099cfc37d348bdd003f064a45c6959478807f7cb6263fc539b2616e13117fc82303aba9afeed67104c344f971a182e741436bcc040468fa6307bb478ed8ef47397e603fe7d50d3c56f5c50c4410db470e21c04b5b883a94c78bc19587d8a611c701d51b956ae06e92987ecdcd5237a65725fdcf7b52a9085831c7214b1282fbd1faf81277a6519c941907f0ef838576e610272512d1860a07a19ac9aec33168b06c937d1ead2e63e58345252be0a5fe15d38a371a6347355d16104b19f1d4a4c18e07dba72bbf8c8150bade347831b446f2ad9f5af4aa91cd35ddf3c64b3f0690c77634efd4ef07b817c6bb90a29f917769cd2978d37a14ed61b437858c93100f55b82ac7f94c0f83ac68a54df1e4d2c54b9c96661f7a41ed9fff8b28fc31949a07a7b298b5819ac7fe7c9490b20a1107ba6f6b94e6d1a58fab09bed6d096edaaea887addea1b7735228bdff409674c0f8bfceb815cdbae6fc5de41379ff842eebe205611c58d80371feb97b1f2a23fb65a474665f991113407da1c0a6f6d1fa7b6ad659f45354178270f5541eb948cbc861c53705982fa67f88c70cb936e86bf45bb17d6214ab716dc0c5fa3b7f3a181e23fad244741c1b6c44c557cf0e4dfe7a1acf75d39bfb810a798b33bcbfc73d9a11b0c648c84ed6e28b27e4aa8681aa33c0779e01ac2d16f58031d92fae578d92b6ab5d7c98a47b64ead0ad4f036922b4604d1b23dd4f4b798bae917294fc458000e7ce1449060a98c62bb1d86a0841f26e70550778103fcc3d70b9adcdb32cc750016b88178513e94d61eb65c8381532feb3368f3db60638f3b88575dded95eb92910d16c51351e7379e5df4f1fd2ffe1b80b958ef6f189ff0483a802bd1c49ed56131e1cc02dbe0949251adbc980b5a7f45317bab8be87961b6b9496d24779023f77dd1b4552fc0b522e857f38e9c63349955e3be4b3a15e86aa96943900797b8139cbfdcd940903ce41249b9e7e4a933b88a204b1ded0e2ba0fa6a1cf3ba4c3ffcc0ca96b06b400051d66ede7013ccc453e4f042e17e1091a536456460fe3551f36c99c05d59105802ee93d645167ee586e71fcc23c33d2362754065955eca45f294bb28f38960826166dfdde409f76f75366234b0a08f6e49caa1c2169e8d9376673e9835dc709bb94e4d8a164ee6278998e891e05086ad8996e496e833d975cb3d4fceb3f7203a7b365891227448b2e968b5bb653b29741bad9ba107b417354e6e3e5acf5710a346e232e9f02606a31bd8b692a67578637a5ff82339dde31425ad59ada17360184f24e6b542810ec43ef11d6a8b2eba53b288ca445d1e1a0daf059320978160eb610631a9b382291418309fc3ceecf3a08900ff5d301044a3cf565567129b10dc2aa970f43493920c4ef3df1c408e12bbcfb01573b79e296e34912cde62716033f73cf74f42b0fde655f2848bec39fd63f3485499147fe170861e9e0eae0e90d400328b595456b6451dc07eac7c8f6849dc065bb7f5ac49cff1530658bcc4d09d03e2f9611a5561dc3a90b3b2f3d933dc110db73904aeee5abcd2362a0e1b045c4cf22334e32fd33193fbfa1c4cb9e3182fb2357cf7a800f552b87579cc41d99",
"signature_hash": "686cc5232f8d0d90c6a447b10b5296c98b0b4ad5e2f88f278a6bd8f3eeb13dbf",
"tx": {
"version": 2,
"unlock_time": 0,
"vin": [
{
"key": {
"amount": 0,
"key_offsets": [43046249, 275416, 9860, 4749, 6529, 31705, 12848, 11038, 1547, 1461, 29],
"k_image": "ea8103138a37c5543f3c632ef80331940cabeba29b758045db328d8d8a99de38"
}
}
],
"vout": [
{
"amount": 0,
"target": {
"key": "5155f659da61b507b0b8591cbef0ba1534b9db29a69be4a933f7897e58870b25"
}
},
{
"amount": 0,
"target": {
"key": "002de4643160fb8351a841f8079aa5af2ac62c9631bb2d5a48fcdb564cb699d1"
}
}
],
"extra": [ 1, 234, 170, 90, 203, 160, 188, 68, 101, 125, 167, 131, 144, 61, 61, 231, 254, 191, 113, 36, 174, 3, 207, 87, 137, 56, 36, 64, 104, 180, 117, 217, 6, 2, 9, 1, 218, 1, 144, 185, 52, 102, 151, 158],
"rct_signatures": {
"type": 5,
"txnFee": 7600000,
"ecdhInfo": [ {
"amount": "7eafd43a457b940e"
}, {
"amount": "592f261bd165059a"
}],
"outPk": [ "9d3b92aa3baaed18346157d1361e30c553df17940985b375fc89b9c09d613d5d", "af007ed1ce05128cfc4d37181a5f2d6f56690357032dbcc6e85424e5ad793388"]
},
"rctsig_prunable": {
"nbp": 1,
"bp": [ {
"A": "df166e5b93e427f8b86079e0de0695d24a4ac5098d98876da5f892359c982015",
"S": "fc0461847fd2955f6099cfc37d348bdd003f064a45c6959478807f7cb6263fc5",
"T1": "39b2616e13117fc82303aba9afeed67104c344f971a182e741436bcc040468fa",
"T2": "6307bb478ed8ef47397e603fe7d50d3c56f5c50c4410db470e21c04b5b883a94",
"taux": "c78bc19587d8a611c701d51b956ae06e92987ecdcd5237a65725fdcf7b52a908",
"mu": "5831c7214b1282fbd1faf81277a6519c941907f0ef838576e610272512d1860a",
"L": [ "a19ac9aec33168b06c937d1ead2e63e58345252be0a5fe15d38a371a6347355d", "16104b19f1d4a4c18e07dba72bbf8c8150bade347831b446f2ad9f5af4aa91cd", "35ddf3c64b3f0690c77634efd4ef07b817c6bb90a29f917769cd2978d37a14ed", "61b437858c93100f55b82ac7f94c0f83ac68a54df1e4d2c54b9c96661f7a41ed", "9fff8b28fc31949a07a7b298b5819ac7fe7c9490b20a1107ba6f6b94e6d1a58f", "ab09bed6d096edaaea887addea1b7735228bdff409674c0f8bfceb815cdbae6f", "c5de41379ff842eebe205611c58d80371feb97b1f2a23fb65a474665f9911134"],
"R": [ "da1c0a6f6d1fa7b6ad659f45354178270f5541eb948cbc861c53705982fa67f8", "8c70cb936e86bf45bb17d6214ab716dc0c5fa3b7f3a181e23fad244741c1b6c4", "4c557cf0e4dfe7a1acf75d39bfb810a798b33bcbfc73d9a11b0c648c84ed6e28", "b27e4aa8681aa33c0779e01ac2d16f58031d92fae578d92b6ab5d7c98a47b64e", "ad0ad4f036922b4604d1b23dd4f4b798bae917294fc458000e7ce1449060a98c", "62bb1d86a0841f26e70550778103fcc3d70b9adcdb32cc750016b88178513e94", "d61eb65c8381532feb3368f3db60638f3b88575dded95eb92910d16c51351e73"],
"a": "79e5df4f1fd2ffe1b80b958ef6f189ff0483a802bd1c49ed56131e1cc02dbe09",
"b": "49251adbc980b5a7f45317bab8be87961b6b9496d24779023f77dd1b4552fc0b",
"t": "522e857f38e9c63349955e3be4b3a15e86aa96943900797b8139cbfdcd940903"
}],
"CLSAGs": [ {
"s": [ "ce41249b9e7e4a933b88a204b1ded0e2ba0fa6a1cf3ba4c3ffcc0ca96b06b400", "051d66ede7013ccc453e4f042e17e1091a536456460fe3551f36c99c05d59105", "802ee93d645167ee586e71fcc23c33d2362754065955eca45f294bb28f389608", "26166dfdde409f76f75366234b0a08f6e49caa1c2169e8d9376673e9835dc709", "bb94e4d8a164ee6278998e891e05086ad8996e496e833d975cb3d4fceb3f7203", "a7b365891227448b2e968b5bb653b29741bad9ba107b417354e6e3e5acf5710a", "346e232e9f02606a31bd8b692a67578637a5ff82339dde31425ad59ada173601", "84f24e6b542810ec43ef11d6a8b2eba53b288ca445d1e1a0daf059320978160e", "b610631a9b382291418309fc3ceecf3a08900ff5d301044a3cf565567129b10d", "c2aa970f43493920c4ef3df1c408e12bbcfb01573b79e296e34912cde6271603", "3f73cf74f42b0fde655f2848bec39fd63f3485499147fe170861e9e0eae0e90d"],
"c1": "400328b595456b6451dc07eac7c8f6849dc065bb7f5ac49cff1530658bcc4d09",
"D": "d03e2f9611a5561dc3a90b3b2f3d933dc110db73904aeee5abcd2362a0e1b045"
}],
"pseudoOuts": [ "c4cf22334e32fd33193fbfa1c4cb9e3182fb2357cf7a800f552b87579cc41d99"]
}
}
},
{
"id": "2f650db5bafd37ce8982f37ee443f2ecf0a8f08f639591583aecb6cd74d5a80c",
"hex": "020001020010d6f68721ea820c88d539a68f0b84af09d19401c08a02f0ee048250c219958401a49f02b33fa321a527dd227f94e759b07b2c025ce22a57db0cb062bfd1f50f6086b14ca3742730c7fa9e5d040003fcdf91296bb4560335835fda30804a7d8d200acfabe4e98a0c425d38556dac06870003d66821247fe13266bad423e445ddd6a1b51a86198e38049e2c8039ab6d5dc8b485000393ae131b8c649288a9fb61ebffa8ecb0fababa8f5159286f895f5bed10bad6388600038abfdfa2d445934fe750607f9654e02389c056644453c942d1841bbf418d94e22021014004716b1c1ffb8447e0c1d27f147a4691ed393fdf2eadb225ebfd54ffdf872f0680d5f814756596945ca3852476b456ac3c9942c978d0a3bcd9e6c236efadcdf54e6ed0db9c4bc6ac562b6859a40ad8f3bc85ca35c98badb4b4c5d43832f330d6fedb08e8f9e2acd339c648bf03957cb02aa69b8ab15326e3bbe1ce35df677306edabd89e5635f226a743068500e25028fbdbf1ea19d0921a27c8baec842b753080f407ee4b9a87f2c525e9bfb61fb4d14187c0577e799bf20e53a86359cb75f40ee4d291017c2b59e7278c94b6296dee9ac65ed5ccf61a77ba4f1b3edfb13c5d02748763b23a6bac2f6a891b474d55b625030b35f9b7b564e747afd4cb8e1ce830a9bc59fd6e146443965494f94a8433de054080fcb71f8d48803598cc91db3c7b3fd190ea8ff5f67980a63de4cb9cd06568a9b27aa994992bc33d70990225acb09faf68066aa27c1118c685cb8f3516c3b664450fabdced384de01650d6455287bc0f210aaa5c173c491844155736a64d7cbdda79f0c8a5ccc07d187ca112664a0e6eb500087178983179f1ba2ffb030d577638001b58f5e621b4723e5b0bd0853fb430113d03efd026660a18a23c7582e9788f770212b604759aa242b35b3ca4a835bb18881c8593ac4247ba51ea95946cf079721588bac494f563a687fe1010818caa94583969b0f4a4a40eeee395cbb7881a53d98cad51b1e5d12c7071a7424b4c534e32c53a31b11e6151edd0a13ef9695021bff9bd4c62df9a62d9e0fbd01e750d0b6abc56cc96d55ef06f6428b42fc63f6610633ecf023211e64a1ff89dcabfeeb4b938e64312dcc849929e8d4a290eff601e06dc65141665d7b312ac1f0f859a00fd6d6ccf7dc695e7ae3cf44bed1d9c8659ee3451dd3498f462912ba881a473c9bc0866e4fb33114b2ef7c25869f9cc3c40a06fc2407e2c678126ff1c38a35c5c949bc219fb33ba15730510c41554c727d5adfce33a518148234e0aa5411cb20c115e749792ad47ee19e9f1544dba61593d95cb98d4720a8ae6e60146416d673e5707c3de31d91043422ab848d4676a6845ced6e7075c5a09bc8b4e0ad706c8c07bda527a7325771438e04f37517f3ca5262ef2ddfc9e13db988a90c50be5422a83ad75b93f4faae980d6e6a3abfd0e96387121101afaf55f425dc876d9a8735c1e29d823e19fee5e502c18d16ec9225f232cfbc3dcd143aaa1904f42e880b612beeea3e5a745a7f32e6b2135a75f71117e2947c99647f14702417a9a76f6130b5d62fd149a606061709a86253c3c2a30c8ccc0e2b5ee636bda81973b011fa8b96e0f9149e7d02d903e982b025e0944029423ba9318637387d6f0a8a75f1fa957950ce6661368738251a418968ae390143e596a77bef7de4008ca66ed28b82e044d0ab293f792e8b1e9c1bc24b14ee53539f535b05f2f336c1b7698ca3cb1dc8a3a09568c6841724a19d412d4313760e3560616df7f5b2250b1a52bf32922b3964309b0bedb645579ee09d87959f4e997e4792ac9fa26858ef1aa1dbf7b10da08e7092cb200369d75f3d2b81ad2c237954cdfea1d173f84122ce4cf82a9ebaa04650a69f3675f2155bbb7ce508fdd6a328492b8788e37809f2accf082387b97a7660d427cac9eb93ceacda0cdb9db95a2d6c6fa9ca86276acce2cb8e432b14efb4d0e8a1f3cbc8534c5dfb9a42f7b0d5c212928115cb2c5b905c650b5325e2a849109c60329dcc20f1c1f10d9f6a87d17359938c520e00dd3f5e1857b5af502cc590cad89abca61f4a94513d8e42db9e7223b5d97afd80f490155bf49b79c7ea5c10d6cb74ba10211d6ec75458436a08794164d16bcb4d092274061449418d9fc3d0a9947a8854a399c7e77a49568676ff8df07c3aa21ca90a611dcdfe0c6bd44690a43a3263237f1def6658ba936e2f17c3853fdcd2c0e24cc0b26c59abb47031e00992ca59657da958b48d21d12ae0a93a68596b72c6cc826fd8e079de67b0539026a24c5dcea4875f16cd0722352424493647f7ad3b3148bcdf6c8504c25bbbb07a8b01a6352cb602a1964c02e7e10601644cee41c2bdbb39a9687fdd78dca919726312d076b9e7a4e5b0324e305b99bb1c3ea40bd2296de41f2fc43f668e1a9fb",
"signature_hash": "9c13c702e03b54a3000a008e4deb1763d7e232c3378bf928df1e2e976f5ba9c5",
"tx": {
"version": 2,
"unlock_time": 0,
"vin": [ {
"key": {
"amount": 0,
"key_offsets": [69335894, 196970, 944776, 182182, 153476, 19025, 34112, 79728, 10242, 3266, 16917, 36772, 8115, 4259, 5029, 4445],
"k_image": "7f94e759b07b2c025ce22a57db0cb062bfd1f50f6086b14ca3742730c7fa9e5d"
}
}],
"vout": [
{
"amount": 0,
"target": {
"tagged_key": {
"key": "fcdf91296bb4560335835fda30804a7d8d200acfabe4e98a0c425d38556dac06",
"view_tag": "87"
}
}
},
{
"amount": 0,
"target": {
"tagged_key": {
"key": "d66821247fe13266bad423e445ddd6a1b51a86198e38049e2c8039ab6d5dc8b4",
"view_tag": "85"
}
}
},
{
"amount": 0,
"target": {
"tagged_key": {
"key": "93ae131b8c649288a9fb61ebffa8ecb0fababa8f5159286f895f5bed10bad638",
"view_tag": "86"
}
}
}, {
"amount": 0,
"target": {
"tagged_key": {
"key": "8abfdfa2d445934fe750607f9654e02389c056644453c942d1841bbf418d94e2",
"view_tag": "20"
}
}
}
],
"extra": [ 1, 64, 4, 113, 107, 28, 31, 251, 132, 71, 224, 193, 210, 127, 20, 122, 70, 145, 237, 57, 63, 223, 46, 173, 178, 37, 235, 253, 84, 255, 223, 135, 47],
"rct_signatures": {
"type": 6,
"txnFee": 43920000,
"ecdhInfo": [ {
"amount": "756596945ca38524"
}, {
"amount": "76b456ac3c9942c9"
}, {
"amount": "78d0a3bcd9e6c236"
}, {
"amount": "efadcdf54e6ed0db"
}],
"outPk": [ "9c4bc6ac562b6859a40ad8f3bc85ca35c98badb4b4c5d43832f330d6fedb08e8", "f9e2acd339c648bf03957cb02aa69b8ab15326e3bbe1ce35df677306edabd89e", "5635f226a743068500e25028fbdbf1ea19d0921a27c8baec842b753080f407ee", "4b9a87f2c525e9bfb61fb4d14187c0577e799bf20e53a86359cb75f40ee4d291"]
},
"rctsig_prunable": {
"nbp": 1,
"bpp": [ {
"A": "7c2b59e7278c94b6296dee9ac65ed5ccf61a77ba4f1b3edfb13c5d02748763b2",
"A1": "3a6bac2f6a891b474d55b625030b35f9b7b564e747afd4cb8e1ce830a9bc59fd",
"B": "6e146443965494f94a8433de054080fcb71f8d48803598cc91db3c7b3fd190ea",
"r1": "8ff5f67980a63de4cb9cd06568a9b27aa994992bc33d70990225acb09faf6806",
"s1": "6aa27c1118c685cb8f3516c3b664450fabdced384de01650d6455287bc0f210a",
"d1": "aa5c173c491844155736a64d7cbdda79f0c8a5ccc07d187ca112664a0e6eb500",
"L": [ "7178983179f1ba2ffb030d577638001b58f5e621b4723e5b0bd0853fb430113d", "03efd026660a18a23c7582e9788f770212b604759aa242b35b3ca4a835bb1888", "1c8593ac4247ba51ea95946cf079721588bac494f563a687fe1010818caa9458", "3969b0f4a4a40eeee395cbb7881a53d98cad51b1e5d12c7071a7424b4c534e32", "c53a31b11e6151edd0a13ef9695021bff9bd4c62df9a62d9e0fbd01e750d0b6a", "bc56cc96d55ef06f6428b42fc63f6610633ecf023211e64a1ff89dcabfeeb4b9", "38e64312dcc849929e8d4a290eff601e06dc65141665d7b312ac1f0f859a00fd", "6d6ccf7dc695e7ae3cf44bed1d9c8659ee3451dd3498f462912ba881a473c9bc"
],
"R": [ "66e4fb33114b2ef7c25869f9cc3c40a06fc2407e2c678126ff1c38a35c5c949b", "c219fb33ba15730510c41554c727d5adfce33a518148234e0aa5411cb20c115e", "749792ad47ee19e9f1544dba61593d95cb98d4720a8ae6e60146416d673e5707", "c3de31d91043422ab848d4676a6845ced6e7075c5a09bc8b4e0ad706c8c07bda", "527a7325771438e04f37517f3ca5262ef2ddfc9e13db988a90c50be5422a83ad", "75b93f4faae980d6e6a3abfd0e96387121101afaf55f425dc876d9a8735c1e29", "d823e19fee5e502c18d16ec9225f232cfbc3dcd143aaa1904f42e880b612beee", "a3e5a745a7f32e6b2135a75f71117e2947c99647f14702417a9a76f6130b5d62"
]
}],
"CLSAGs": [ {
"s": [ "fd149a606061709a86253c3c2a30c8ccc0e2b5ee636bda81973b011fa8b96e0f", "9149e7d02d903e982b025e0944029423ba9318637387d6f0a8a75f1fa957950c", "e6661368738251a418968ae390143e596a77bef7de4008ca66ed28b82e044d0a", "b293f792e8b1e9c1bc24b14ee53539f535b05f2f336c1b7698ca3cb1dc8a3a09", "568c6841724a19d412d4313760e3560616df7f5b2250b1a52bf32922b3964309", "b0bedb645579ee09d87959f4e997e4792ac9fa26858ef1aa1dbf7b10da08e709", "2cb200369d75f3d2b81ad2c237954cdfea1d173f84122ce4cf82a9ebaa04650a", "69f3675f2155bbb7ce508fdd6a328492b8788e37809f2accf082387b97a7660d", "427cac9eb93ceacda0cdb9db95a2d6c6fa9ca86276acce2cb8e432b14efb4d0e", "8a1f3cbc8534c5dfb9a42f7b0d5c212928115cb2c5b905c650b5325e2a849109", "c60329dcc20f1c1f10d9f6a87d17359938c520e00dd3f5e1857b5af502cc590c", "ad89abca61f4a94513d8e42db9e7223b5d97afd80f490155bf49b79c7ea5c10d", "6cb74ba10211d6ec75458436a08794164d16bcb4d092274061449418d9fc3d0a", "9947a8854a399c7e77a49568676ff8df07c3aa21ca90a611dcdfe0c6bd44690a", "43a3263237f1def6658ba936e2f17c3853fdcd2c0e24cc0b26c59abb47031e00", "992ca59657da958b48d21d12ae0a93a68596b72c6cc826fd8e079de67b053902"],
"c1": "6a24c5dcea4875f16cd0722352424493647f7ad3b3148bcdf6c8504c25bbbb07",
"D": "a8b01a6352cb602a1964c02e7e10601644cee41c2bdbb39a9687fdd78dca9197"
}],
"pseudoOuts": [ "26312d076b9e7a4e5b0324e305b99bb1c3ea40bd2296de41f2fc43f668e1a9fb"]
}
}
},
{
"id": "f66f36be5a6b340bc8515d3606d4beceb20611dddb1802b387fbaba30c5c98d3",
"hex": "02000102000bf59ea50bf48bfb08e1d6a1039843f7ee0597d002ba3ca603de3be263ca194830cafb5a73ad93cd2fe5271505596a75d7cabb01ced2bb608028245ea73bb8020002fc3f396be673a4957fbc1976601941d225ffdbec54bc06461698d14fda7c8b1f00022757dd54027e93c917251de2cc6777f7a3fa484f5b244ab54bf8783e7da80c362c01959377b2cc5b76f40886262064cc71324414c2996720dcbea25eae8b8faf4f9802090126a37bb1d1414ab705c0cef71cf0704cd0b1fcde1d656737377f5106deb167d7cde7206c17c8f2f25a508be29e1ad78bb792c3fedcbdd9ce95815c59a5fd98ff5b251f105f3d51067fb90cb9f1e0b6138dfda82aad4906472cf7f8c1b501c786dc1c545d39b00502134e4a2935b9b81f420f4d926bed61b2dde30fde4a464d85cbfda1df5a07da2d4d135ff618e5cf4d6b22238b913af712dd59cb228fec35fc0639d3b54edf518e507034b5be35523ac3c98396bd9cc6e6a59e1c6eda6e93f9e84c4fdade80a6f449ac6ccf8d6583fcd495b78c53a43321210d73370f86999fcc79761f1810514b50b8a1fee1288289b64718d54bcef42abfe61fb8fb0f60373d190d80ad65cabd36d9600d6253f8d343961367526122ca0c5b0acdc60c071f5ab47025d4d89568a2f0b56fc73c6488cc4500e398d2b3059e0d35cede35c33b473e6e57121629db88f0f8c15e03036eae2887f1d9d76d90a0b5a5caa01272347c96e88e461acb04a6be5624c4a6742dd8a0d36ac75e7056ffb3a3a7ae68dda87894c7f503a794dc4458ff058e6f7cd903662e5961d5eeb052b8f075a5ad5cb84407f96ce47a5793ad0c8e4060ee4d90c9946c54b83e91737ccf71acc00045a919866586941d8deeb467a7d83335f84a10d66ba1f51afdf961649ac95ad97dd24d553c1c1004a73332d225d29d0c62bee22e2ea81ceffb02f27ff05a12144a076ed84bce66dd6e84d56fedf06c180a504706977084ff0a74174da623fcb03a88b0246b5a76de445334f447c4525e524e0d3e6b8751417249e0eb6f498ba12ccee562dfddb048ddbd5131263e210d54f04ec38a41c64f7a5812c2083b9fc46fecc2de7f0d1d0aeaaa2f2a9d956879f66e563b48ff9476d67ab98f6956019bf0a36428b0361b28383e7ef2b90bad6a66ae286af4753e54fe6be3131fdc9986ccdf04cbc167abdfd181b326a1d8b30990b9df0b1e702c7bab83c199aa1341c6b4ae1e227b831f0d068e619bb73ed5de32b0bfda62c5203be7f2a6a37c3feb4663da413387e0255ec0204e469fed5e0fde75662cf3a90ae0044778a5a75a319dec07d2d84c70c9b51c9a31f9bc7f25f1a89033dbd23072ac38eb59e08d6ad4f321011a08df0f0559d3b6dd8a22042b40be532d2fc811e42774cd129dc9c9e671600c3a7fe60336836a1e71f2bfeee69d2ae5e647b54dc1c54e9993a64c0f42daf70aaab2387b9a0fdcc82a7e2f3b52ed2a8135b55f166cb49fb6b1d34a64d30f370d8271408d0e4db75616b758014671d321c8c5086a8d7b1bedd44bcb75b382c60bffb7c7726232426ea19c9bd622ee096e772a4c5ab6305a6b2f27fb4a5f60e40737a4cc043ea061755ace64393a0af82ea8088307426acb33a34de95ea7252c01a5b07847f707777b8cd64cc73364a8e65181227ba1ba5aa63161408a7265980b6b6c18079d195a12ec7af4404ff61d3c756aa35b88e4fe4bd72c8b22298b1601b1d04f9861e7118f10808505812809d54d85aa79f4ceb905c8e87b1a5801c60bb12b0f3fdc0e5e0afb7839fd51742aa88ef4466bc1e1f9c1b6f978f736b1880c9a902621eaca740aa21cfaf36931e09b7cc3b28223ad6c2398bd828c7270460d78dd6dd5d9c181aed832e62d56b00e870961b0b6a3a77eb4604cef64f69898046ba7157673909c3b2cb3bedef665e83c364475d482dfa46e717c4c5fa29b7f09dda97110c8a66b48e8fab4cbff2578e4cb85e6b353bfe8f78b6f9182711de13c7093d2007f1dbdfe5f46332b797376af80efc67ab028c597d528461384683dae",
"signature_hash": "8cb405e1460df8134032db1430e1cfffb8f707c9de43ba1f68100f2af8a5e6b1",
"tx": {
"version": 2,
"unlock_time": 0,
"vin": [ {
"key": {
"amount": 0,
"key_offsets": [ 23678837, 18793972, 6843233, 8600, 96119, 43031, 7738, 422, 7646, 12770, 3274],
"k_image": "4830cafb5a73ad93cd2fe5271505596a75d7cabb01ced2bb608028245ea73bb8"
}
}],
"vout": [
{
"amount": 0,
"target": {
"key": "fc3f396be673a4957fbc1976601941d225ffdbec54bc06461698d14fda7c8b1f"
}
},
{
"amount": 0,
"target": {
"key": "2757dd54027e93c917251de2cc6777f7a3fa484f5b244ab54bf8783e7da80c36"
}
}
],
"extra": [ 1, 149, 147, 119, 178, 204, 91, 118, 244, 8, 134, 38, 32, 100, 204, 113, 50, 68, 20, 194, 153, 103, 32, 220, 190, 162, 94, 174, 139, 143, 175, 79, 152, 2, 9, 1, 38, 163, 123, 177, 209, 65, 74, 183],
"rct_signatures": {
"type": 5,
"txnFee": 60680000,
"ecdhInfo": [ {
"amount": "f0704cd0b1fcde1d"
}, {
"amount": "656737377f5106de"
}],
"outPk": [ "b167d7cde7206c17c8f2f25a508be29e1ad78bb792c3fedcbdd9ce95815c59a5", "fd98ff5b251f105f3d51067fb90cb9f1e0b6138dfda82aad4906472cf7f8c1b5"]
},
"rctsig_prunable": {
"nbp": 1,
"bp": [ {
"A": "c786dc1c545d39b00502134e4a2935b9b81f420f4d926bed61b2dde30fde4a46",
"S": "4d85cbfda1df5a07da2d4d135ff618e5cf4d6b22238b913af712dd59cb228fec",
"T1": "35fc0639d3b54edf518e507034b5be35523ac3c98396bd9cc6e6a59e1c6eda6e",
"T2": "93f9e84c4fdade80a6f449ac6ccf8d6583fcd495b78c53a43321210d73370f86",
"taux": "999fcc79761f1810514b50b8a1fee1288289b64718d54bcef42abfe61fb8fb0f",
"mu": "60373d190d80ad65cabd36d9600d6253f8d343961367526122ca0c5b0acdc60c",
"L": [ "1f5ab47025d4d89568a2f0b56fc73c6488cc4500e398d2b3059e0d35cede35c3", "3b473e6e57121629db88f0f8c15e03036eae2887f1d9d76d90a0b5a5caa01272", "347c96e88e461acb04a6be5624c4a6742dd8a0d36ac75e7056ffb3a3a7ae68dd", "a87894c7f503a794dc4458ff058e6f7cd903662e5961d5eeb052b8f075a5ad5c", "b84407f96ce47a5793ad0c8e4060ee4d90c9946c54b83e91737ccf71acc00045", "a919866586941d8deeb467a7d83335f84a10d66ba1f51afdf961649ac95ad97d", "d24d553c1c1004a73332d225d29d0c62bee22e2ea81ceffb02f27ff05a12144a"
],
"R": [ "6ed84bce66dd6e84d56fedf06c180a504706977084ff0a74174da623fcb03a88", "b0246b5a76de445334f447c4525e524e0d3e6b8751417249e0eb6f498ba12cce", "e562dfddb048ddbd5131263e210d54f04ec38a41c64f7a5812c2083b9fc46fec", "c2de7f0d1d0aeaaa2f2a9d956879f66e563b48ff9476d67ab98f6956019bf0a3", "6428b0361b28383e7ef2b90bad6a66ae286af4753e54fe6be3131fdc9986ccdf", "04cbc167abdfd181b326a1d8b30990b9df0b1e702c7bab83c199aa1341c6b4ae", "1e227b831f0d068e619bb73ed5de32b0bfda62c5203be7f2a6a37c3feb4663da"
],
"a": "413387e0255ec0204e469fed5e0fde75662cf3a90ae0044778a5a75a319dec07",
"b": "d2d84c70c9b51c9a31f9bc7f25f1a89033dbd23072ac38eb59e08d6ad4f32101",
"t": "1a08df0f0559d3b6dd8a22042b40be532d2fc811e42774cd129dc9c9e671600c"
}],
"CLSAGs": [ {
"s": [ "3a7fe60336836a1e71f2bfeee69d2ae5e647b54dc1c54e9993a64c0f42daf70a", "aab2387b9a0fdcc82a7e2f3b52ed2a8135b55f166cb49fb6b1d34a64d30f370d", "8271408d0e4db75616b758014671d321c8c5086a8d7b1bedd44bcb75b382c60b", "ffb7c7726232426ea19c9bd622ee096e772a4c5ab6305a6b2f27fb4a5f60e407", "37a4cc043ea061755ace64393a0af82ea8088307426acb33a34de95ea7252c01", "a5b07847f707777b8cd64cc73364a8e65181227ba1ba5aa63161408a7265980b", "6b6c18079d195a12ec7af4404ff61d3c756aa35b88e4fe4bd72c8b22298b1601", "b1d04f9861e7118f10808505812809d54d85aa79f4ceb905c8e87b1a5801c60b", "b12b0f3fdc0e5e0afb7839fd51742aa88ef4466bc1e1f9c1b6f978f736b1880c", "9a902621eaca740aa21cfaf36931e09b7cc3b28223ad6c2398bd828c7270460d", "78dd6dd5d9c181aed832e62d56b00e870961b0b6a3a77eb4604cef64f6989804"],
"c1": "6ba7157673909c3b2cb3bedef665e83c364475d482dfa46e717c4c5fa29b7f09",
"D": "dda97110c8a66b48e8fab4cbff2578e4cb85e6b353bfe8f78b6f9182711de13c"
}],
"pseudoOuts": [ "7093d2007f1dbdfe5f46332b797376af80efc67ab028c597d528461384683dae"]
}
}
},
{
"id": "55ba10662968c57fc8fed2c82a99d6fd9516730c245f58e9e87bb9a35378014a",
"hex": "01000302b0f9cf0e0100e53d3d97d11974ccf49d23513b9465bc139bda14b8207288e41557707e59c2dc02c08092de06010133e69f524f1989738827c9fb9087d45d7b6865645453f620189939d926735b3902b0b2c4f62201001d680e360c156c7cc952b0c4fc39a39c95a766423109766ae3dcf0c5cf8abf9e06a0f736026bcac41f5468fd1bcf8994e1a4282aecb420005ec294324faec5c6f46806614780c8afa025027998ef0ac319d96b224fa3c33fe12677ef06f4aed80916f28e3f7a684a1c89d78094ebdc03028e0004b98f7c622f1d0364a2d0270c40e6606793bd948a41af9287016d264d7580ade20402f911ad66eabcd8112e90130d7594cb8fc413431da9b5b004c8651d100a2ac8fb8084af5f029caa24c41b1b4938e7e066a7e59592fdc3c832e2516048e57af5d8acca9bef0ac0b80202e99b6a2e000f03f73c1966e16875945ffad20b1895efa17202c6e240191b01e421011da6ff966df43bd44f513aaafa260c54e2ad31469664fa7b0a44fed4ead9a483056cd88c350b340289694ad525f1316367ed16673dc23911e624e6bac4a48b032a623d09ce5bd85a839534de4fbbfb72da2a6779a66f775c16379e8abd122401a5a19c47bb8ecd0c7aa9610513b611602d246f3b07fa19512dc8fde7b180c704694b2d75abbaa2907cda52f888f18ded34308dd1b40f1fb33a01340a1ddc5f07ce19ab4357c52764600861d2d331cfed5972fff42dac64583d617fa4ee27a509a8c29ff77fc71bc7cd106ce54cd3020d1c18c6c794aaa93cb32cd4ff55d82b04",
"signature_hash": "55ba10662968c57fc8fed2c82a99d6fd9516730c245f58e9e87bb9a35378014a",
"tx": {
"version": 1,
"unlock_time": 0,
"vin": [
{
"key": {
"amount": 30670000,
"key_offsets": [0],
"k_image": "e53d3d97d11974ccf49d23513b9465bc139bda14b8207288e41557707e59c2dc"
}
},
{
"key": {
"amount": 1808040000,
"key_offsets": [1],
"k_image": "33e69f524f1989738827c9fb9087d45d7b6865645453f620189939d926735b39"
}
},
{
"key": {
"amount": 9375390000,
"key_offsets": [0],
"k_image": "1d680e360c156c7cc952b0c4fc39a39c95a766423109766ae3dcf0c5cf8abf9e"
}
}
],
"vout": [
{
"amount": 900000,
"target": {
"key": "6bcac41f5468fd1bcf8994e1a4282aecb420005ec294324faec5c6f468066147"
}
},
{
"amount": 10000000000,
"target": {
"key": "7998ef0ac319d96b224fa3c33fe12677ef06f4aed80916f28e3f7a684a1c89d7"
}
},
{
"amount": 1000000000,
"target": {
"key": "8e0004b98f7c622f1d0364a2d0270c40e6606793bd948a41af9287016d264d75"
}
},
{
"amount": 10000000,
"target": {
"key": "f911ad66eabcd8112e90130d7594cb8fc413431da9b5b004c8651d100a2ac8fb"
}
},
{
"amount": 200000000,
"target": {
"key": "9caa24c41b1b4938e7e066a7e59592fdc3c832e2516048e57af5d8acca9bef0a"
}
},
{
"amount": 40000,
"target": {
"key": "e99b6a2e000f03f73c1966e16875945ffad20b1895efa17202c6e240191b01e4"
}
}
],
"extra": [ 1, 29, 166, 255, 150, 109, 244, 59, 212, 79, 81, 58, 170, 250, 38, 12, 84, 226, 173, 49, 70, 150, 100, 250, 123, 10, 68, 254, 212, 234, 217, 164, 131],
"signatures": [ "056cd88c350b340289694ad525f1316367ed16673dc23911e624e6bac4a48b032a623d09ce5bd85a839534de4fbbfb72da2a6779a66f775c16379e8abd122401", "a5a19c47bb8ecd0c7aa9610513b611602d246f3b07fa19512dc8fde7b180c704694b2d75abbaa2907cda52f888f18ded34308dd1b40f1fb33a01340a1ddc5f07", "ce19ab4357c52764600861d2d331cfed5972fff42dac64583d617fa4ee27a509a8c29ff77fc71bc7cd106ce54cd3020d1c18c6c794aaa93cb32cd4ff55d82b04"]
}
}
]

View File

@@ -0,0 +1,625 @@
use core::cmp::Ordering;
use std_shims::{
vec,
vec::Vec,
io::{self, Read, Write},
};
use zeroize::Zeroize;
use curve25519_dalek::edwards::{EdwardsPoint, CompressedEdwardsY};
use crate::{
io::*,
primitives::keccak256,
ring_signatures::RingSignature,
ringct::{bulletproofs::Bulletproof, PrunedRctProofs},
};
/// An input in the Monero protocol.
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum Input {
/// An input for a miner transaction, which is generating new coins.
Gen(usize),
/// An input spending an output on-chain.
ToKey {
/// The pool this input spends an output of.
amount: Option<u64>,
/// The decoys used by this input's ring, specified as their offset distance from each other.
key_offsets: Vec<u64>,
/// The key image (linking tag, nullifer) for the spent output.
key_image: EdwardsPoint,
},
}
impl Input {
/// Write the Input.
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
match self {
Input::Gen(height) => {
w.write_all(&[255])?;
write_varint(height, w)
}
Input::ToKey { amount, key_offsets, key_image } => {
w.write_all(&[2])?;
write_varint(&amount.unwrap_or(0), w)?;
write_vec(write_varint, key_offsets, w)?;
write_point(key_image, w)
}
}
}
/// Serialize the Input to a `Vec<u8>`.
pub fn serialize(&self) -> Vec<u8> {
let mut res = vec![];
self.write(&mut res).unwrap();
res
}
/// Read an Input.
pub fn read<R: Read>(r: &mut R) -> io::Result<Input> {
Ok(match read_byte(r)? {
255 => Input::Gen(read_varint(r)?),
2 => {
let amount = read_varint(r)?;
// https://github.com/monero-project/monero/
// blob/00fd416a99686f0956361d1cd0337fe56e58d4a7/
// src/cryptonote_basic/cryptonote_format_utils.cpp#L860-L863
// A non-RCT 0-amount input can't exist because only RCT TXs can have a 0-amount output
// That's why collapsing to None if the amount is 0 is safe, even without knowing if RCT
let amount = if amount == 0 { None } else { Some(amount) };
Input::ToKey {
amount,
key_offsets: read_vec(read_varint, r)?,
key_image: read_torsion_free_point(r)?,
}
}
_ => Err(io::Error::other("Tried to deserialize unknown/unused input type"))?,
})
}
}
/// An output in the Monero protocol.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Output {
/// The pool this output should be sorted into.
pub amount: Option<u64>,
/// The key which can spend this output.
pub key: CompressedEdwardsY,
/// The view tag for this output, as used to accelerate scanning.
pub view_tag: Option<u8>,
}
impl Output {
/// Write the Output.
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
write_varint(&self.amount.unwrap_or(0), w)?;
w.write_all(&[2 + u8::from(self.view_tag.is_some())])?;
w.write_all(&self.key.to_bytes())?;
if let Some(view_tag) = self.view_tag {
w.write_all(&[view_tag])?;
}
Ok(())
}
/// Write the Output to a `Vec<u8>`.
pub fn serialize(&self) -> Vec<u8> {
let mut res = Vec::with_capacity(8 + 1 + 32);
self.write(&mut res).unwrap();
res
}
/// Read an Output.
pub fn read<R: Read>(rct: bool, r: &mut R) -> io::Result<Output> {
let amount = read_varint(r)?;
let amount = if rct {
if amount != 0 {
Err(io::Error::other("RCT TX output wasn't 0"))?;
}
None
} else {
Some(amount)
};
let view_tag = match read_byte(r)? {
2 => false,
3 => true,
_ => Err(io::Error::other("Tried to deserialize unknown/unused output type"))?,
};
Ok(Output {
amount,
key: CompressedEdwardsY(read_bytes(r)?),
view_tag: if view_tag { Some(read_byte(r)?) } else { None },
})
}
}
/// An additional timelock for a Monero transaction.
///
/// Monero outputs are locked by a default timelock. If a timelock is explicitly specified, the
/// longer of the two will be the timelock used.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
pub enum Timelock {
/// No additional timelock.
None,
/// Additionally locked until this block.
Block(usize),
/// Additionally locked until this many seconds since the epoch.
Time(u64),
}
impl Timelock {
/// Write the Timelock.
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
match self {
Timelock::None => write_varint(&0u8, w),
Timelock::Block(block) => write_varint(block, w),
Timelock::Time(time) => write_varint(time, w),
}
}
/// Serialize the Timelock to a `Vec<u8>`.
pub fn serialize(&self) -> Vec<u8> {
let mut res = Vec::with_capacity(1);
self.write(&mut res).unwrap();
res
}
/// Read a Timelock.
pub fn read<R: Read>(r: &mut R) -> io::Result<Self> {
const TIMELOCK_BLOCK_THRESHOLD: usize = 500_000_000;
let raw = read_varint::<_, u64>(r)?;
Ok(if raw == 0 {
Timelock::None
} else if raw <
u64::try_from(TIMELOCK_BLOCK_THRESHOLD)
.expect("TIMELOCK_BLOCK_THRESHOLD didn't fit in a u64")
{
Timelock::Block(usize::try_from(raw).expect(
"timelock overflowed usize despite being less than a const representable with a usize",
))
} else {
Timelock::Time(raw)
})
}
}
impl PartialOrd for Timelock {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match (self, other) {
(Timelock::None, Timelock::None) => Some(Ordering::Equal),
(Timelock::None, _) => Some(Ordering::Less),
(_, Timelock::None) => Some(Ordering::Greater),
(Timelock::Block(a), Timelock::Block(b)) => a.partial_cmp(b),
(Timelock::Time(a), Timelock::Time(b)) => a.partial_cmp(b),
_ => None,
}
}
}
/// The transaction prefix.
///
/// This is common to all transaction versions and contains most parts of the transaction needed to
/// handle it. It excludes any proofs.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct TransactionPrefix {
/// The timelock this transaction is additionally constrained by.
///
/// All transactions on the blockchain are subject to a 10-block lock. This adds a further
/// constraint.
pub additional_timelock: Timelock,
/// The inputs for this transaction.
pub inputs: Vec<Input>,
/// The outputs for this transaction.
pub outputs: Vec<Output>,
/// The additional data included within the transaction.
///
/// This is an arbitrary data field, yet is used by wallets for containing the data necessary to
/// scan the transaction.
pub extra: Vec<u8>,
}
impl TransactionPrefix {
/// Write a TransactionPrefix.
///
/// This is distinct from Monero in that it won't write any version.
fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
self.additional_timelock.write(w)?;
write_vec(Input::write, &self.inputs, w)?;
write_vec(Output::write, &self.outputs, w)?;
write_varint(&self.extra.len(), w)?;
w.write_all(&self.extra)
}
/// Read a TransactionPrefix.
///
/// This is distinct from Monero in that it won't read the version. The version must be passed
/// in.
pub fn read<R: Read>(r: &mut R, version: u64) -> io::Result<TransactionPrefix> {
let additional_timelock = Timelock::read(r)?;
let inputs = read_vec(|r| Input::read(r), r)?;
if inputs.is_empty() {
Err(io::Error::other("transaction had no inputs"))?;
}
let is_miner_tx = matches!(inputs[0], Input::Gen { .. });
let mut prefix = TransactionPrefix {
additional_timelock,
inputs,
outputs: read_vec(|r| Output::read((!is_miner_tx) && (version == 2), r), r)?,
extra: vec![],
};
prefix.extra = read_vec(read_byte, r)?;
Ok(prefix)
}
fn hash(&self, version: u64) -> [u8; 32] {
let mut buf = vec![];
write_varint(&version, &mut buf).unwrap();
self.write(&mut buf).unwrap();
keccak256(buf)
}
}
mod sealed {
use core::fmt::Debug;
use crate::ringct::*;
use super::*;
pub(crate) trait RingSignatures: Clone + PartialEq + Eq + Default + Debug {
fn signatures_to_write(&self) -> &[RingSignature];
fn read_signatures(inputs: &[Input], r: &mut impl Read) -> io::Result<Self>;
}
impl RingSignatures for Vec<RingSignature> {
fn signatures_to_write(&self) -> &[RingSignature] {
self
}
fn read_signatures(inputs: &[Input], r: &mut impl Read) -> io::Result<Self> {
let mut signatures = Vec::with_capacity(inputs.len());
for input in inputs {
match input {
Input::ToKey { key_offsets, .. } => {
signatures.push(RingSignature::read(key_offsets.len(), r)?)
}
_ => Err(io::Error::other("reading signatures for a transaction with non-ToKey inputs"))?,
}
}
Ok(signatures)
}
}
impl RingSignatures for () {
fn signatures_to_write(&self) -> &[RingSignature] {
&[]
}
fn read_signatures(_: &[Input], _: &mut impl Read) -> io::Result<Self> {
Ok(())
}
}
pub(crate) trait RctProofsTrait: Clone + PartialEq + Eq + Debug {
fn write(&self, w: &mut impl Write) -> io::Result<()>;
fn read(
ring_length: usize,
inputs: usize,
outputs: usize,
r: &mut impl Read,
) -> io::Result<Option<Self>>;
fn rct_type(&self) -> RctType;
fn base(&self) -> &RctBase;
}
impl RctProofsTrait for RctProofs {
fn write(&self, w: &mut impl Write) -> io::Result<()> {
self.write(w)
}
fn read(
ring_length: usize,
inputs: usize,
outputs: usize,
r: &mut impl Read,
) -> io::Result<Option<Self>> {
RctProofs::read(ring_length, inputs, outputs, r)
}
fn rct_type(&self) -> RctType {
self.rct_type()
}
fn base(&self) -> &RctBase {
&self.base
}
}
impl RctProofsTrait for PrunedRctProofs {
fn write(&self, w: &mut impl Write) -> io::Result<()> {
self.base.write(w, self.rct_type)
}
fn read(
_ring_length: usize,
inputs: usize,
outputs: usize,
r: &mut impl Read,
) -> io::Result<Option<Self>> {
Ok(RctBase::read(inputs, outputs, r)?.map(|(rct_type, base)| Self { rct_type, base }))
}
fn rct_type(&self) -> RctType {
self.rct_type
}
fn base(&self) -> &RctBase {
&self.base
}
}
pub(crate) trait PotentiallyPruned {
type RingSignatures: RingSignatures;
type RctProofs: RctProofsTrait;
}
/// A transaction which isn't pruned.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct NotPruned;
impl PotentiallyPruned for NotPruned {
type RingSignatures = Vec<RingSignature>;
type RctProofs = RctProofs;
}
/// A transaction which is pruned.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Pruned;
impl PotentiallyPruned for Pruned {
type RingSignatures = ();
type RctProofs = PrunedRctProofs;
}
}
pub use sealed::*;
/// A Monero transaction.
#[allow(private_bounds, private_interfaces, clippy::large_enum_variant)]
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum Transaction<P: PotentiallyPruned = NotPruned> {
/// A version 1 transaction, used by the original Cryptonote codebase.
V1 {
/// The transaction's prefix.
prefix: TransactionPrefix,
/// The transaction's ring signatures.
signatures: P::RingSignatures,
},
/// A version 2 transaction, used by the RingCT protocol.
V2 {
/// The transaction's prefix.
prefix: TransactionPrefix,
/// The transaction's proofs.
proofs: Option<P::RctProofs>,
},
}
enum PrunableHash<'a> {
V1(&'a [RingSignature]),
V2([u8; 32]),
}
#[allow(private_bounds)]
impl<P: PotentiallyPruned> Transaction<P> {
/// Get the version of this transaction.
pub fn version(&self) -> u8 {
match self {
Transaction::V1 { .. } => 1,
Transaction::V2 { .. } => 2,
}
}
/// Get the TransactionPrefix of this transaction.
pub fn prefix(&self) -> &TransactionPrefix {
match self {
Transaction::V1 { prefix, .. } | Transaction::V2 { prefix, .. } => prefix,
}
}
/// Get a mutable reference to the TransactionPrefix of this transaction.
pub fn prefix_mut(&mut self) -> &mut TransactionPrefix {
match self {
Transaction::V1 { prefix, .. } | Transaction::V2 { prefix, .. } => prefix,
}
}
/// Write the Transaction.
///
/// Some writable transactions may not be readable if they're malformed, per Monero's consensus
/// rules.
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
write_varint(&self.version(), w)?;
match self {
Transaction::V1 { prefix, signatures } => {
prefix.write(w)?;
for ring_sig in signatures.signatures_to_write() {
ring_sig.write(w)?;
}
}
Transaction::V2 { prefix, proofs } => {
prefix.write(w)?;
match proofs {
None => w.write_all(&[0])?,
Some(proofs) => proofs.write(w)?,
}
}
}
Ok(())
}
/// Write the Transaction to a `Vec<u8>`.
pub fn serialize(&self) -> Vec<u8> {
let mut res = Vec::with_capacity(2048);
self.write(&mut res).unwrap();
res
}
/// Read a Transaction.
pub fn read<R: Read>(r: &mut R) -> io::Result<Self> {
let version = read_varint(r)?;
let prefix = TransactionPrefix::read(r, version)?;
if version == 1 {
let signatures = if (prefix.inputs.len() == 1) && matches!(prefix.inputs[0], Input::Gen(_)) {
Default::default()
} else {
P::RingSignatures::read_signatures(&prefix.inputs, r)?
};
Ok(Transaction::V1 { prefix, signatures })
} else if version == 2 {
let proofs = P::RctProofs::read(
prefix.inputs.first().map_or(0, |input| match input {
Input::Gen(_) => 0,
Input::ToKey { key_offsets, .. } => key_offsets.len(),
}),
prefix.inputs.len(),
prefix.outputs.len(),
r,
)?;
Ok(Transaction::V2 { prefix, proofs })
} else {
Err(io::Error::other("tried to deserialize unknown version"))
}
}
// The hash of the transaction.
#[allow(clippy::needless_pass_by_value)]
fn hash_with_prunable_hash(&self, prunable: PrunableHash<'_>) -> [u8; 32] {
match self {
Transaction::V1 { prefix, .. } => {
let mut buf = Vec::with_capacity(512);
// We don't use `self.write` as that may write the signatures (if this isn't pruned)
write_varint(&self.version(), &mut buf).unwrap();
prefix.write(&mut buf).unwrap();
// We explicitly write the signatures ourselves here
let PrunableHash::V1(signatures) = prunable else {
panic!("hashing v1 TX with non-v1 prunable data")
};
for signature in signatures {
signature.write(&mut buf).unwrap();
}
keccak256(buf)
}
Transaction::V2 { prefix, proofs } => {
let mut hashes = Vec::with_capacity(96);
hashes.extend(prefix.hash(2));
if let Some(proofs) = proofs {
let mut buf = Vec::with_capacity(512);
proofs.base().write(&mut buf, proofs.rct_type()).unwrap();
hashes.extend(keccak256(&buf));
} else {
// Serialization of RctBase::Null
hashes.extend(keccak256([0]));
}
let PrunableHash::V2(prunable_hash) = prunable else {
panic!("hashing v2 TX with non-v2 prunable data")
};
hashes.extend(prunable_hash);
keccak256(hashes)
}
}
}
}
impl Transaction<NotPruned> {
/// The hash of the transaction.
pub fn hash(&self) -> [u8; 32] {
match self {
Transaction::V1 { signatures, .. } => {
self.hash_with_prunable_hash(PrunableHash::V1(signatures))
}
Transaction::V2 { proofs, .. } => {
self.hash_with_prunable_hash(PrunableHash::V2(if let Some(proofs) = proofs {
let mut buf = Vec::with_capacity(1024);
proofs.prunable.write(&mut buf, proofs.rct_type()).unwrap();
keccak256(buf)
} else {
[0; 32]
}))
}
}
}
/// Calculate the hash of this transaction as needed for signing it.
///
/// This returns None if the transaction is without signatures.
pub fn signature_hash(&self) -> Option<[u8; 32]> {
Some(match self {
Transaction::V1 { prefix, signatures } => {
if (prefix.inputs.len() == 1) && matches!(prefix.inputs[0], Input::Gen(_)) {
None?;
}
self.hash_with_prunable_hash(PrunableHash::V1(signatures))
}
Transaction::V2 { proofs, .. } => self.hash_with_prunable_hash({
let Some(proofs) = proofs else { None? };
let mut buf = Vec::with_capacity(1024);
proofs.prunable.signature_write(&mut buf).unwrap();
PrunableHash::V2(keccak256(buf))
}),
})
}
fn is_rct_bulletproof(&self) -> bool {
match self {
Transaction::V1 { .. } => false,
Transaction::V2 { proofs, .. } => {
let Some(proofs) = proofs else { return false };
proofs.rct_type().bulletproof()
}
}
}
fn is_rct_bulletproof_plus(&self) -> bool {
match self {
Transaction::V1 { .. } => false,
Transaction::V2 { proofs, .. } => {
let Some(proofs) = proofs else { return false };
proofs.rct_type().bulletproof_plus()
}
}
}
/// Calculate the transaction's weight.
pub fn weight(&self) -> usize {
let blob_size = self.serialize().len();
let bp = self.is_rct_bulletproof();
let bp_plus = self.is_rct_bulletproof_plus();
if !(bp || bp_plus) {
blob_size
} else {
blob_size +
Bulletproof::calculate_bp_clawback(
bp_plus,
match self {
Transaction::V1 { .. } => panic!("v1 transaction was BP(+)"),
Transaction::V2 { prefix, .. } => prefix.outputs.len(),
},
)
.0
}
}
}
impl From<Transaction<NotPruned>> for Transaction<Pruned> {
fn from(tx: Transaction<NotPruned>) -> Transaction<Pruned> {
match tx {
Transaction::V1 { prefix, .. } => Transaction::V1 { prefix, signatures: () },
Transaction::V2 { prefix, proofs } => Transaction::V2 {
prefix,
proofs: proofs
.map(|proofs| PrunedRctProofs { rct_type: proofs.rct_type(), base: proofs.base }),
},
}
}
}