Skip to content

Commit

Permalink
types: add derivation helpers (#70)
Browse files Browse the repository at this point in the history
  • Loading branch information
n8maninger authored Dec 2, 2024
1 parent 1a7e7e7 commit 1480562
Show file tree
Hide file tree
Showing 7 changed files with 290 additions and 35 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
sia_sdk: minor
---

# Add ID derivation helpers to transactions, blocks, and siafund claims
11 changes: 5 additions & 6 deletions sia/src/consensus.rs

Large diffs are not rendered by default.

7 changes: 2 additions & 5 deletions sia/src/signing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@ impl PublicKey {

impl Serialize for PublicKey {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
String::serialize(
&format!("{}{}", Self::PREFIX, &self.to_string()),
serializer,
)
String::serialize(&self.to_string(), serializer)
}
}

Expand All @@ -41,7 +38,7 @@ impl<'de> Deserialize<'de> for PublicKey {

impl fmt::Display for PublicKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
hex::encode(self.0).fmt(f)
write!(f, "{}{}", Self::PREFIX, hex::encode(self.0))
}
}

Expand Down
254 changes: 248 additions & 6 deletions sia/src/types/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,139 @@ use crate::macros::impl_hash_id;
use crate::types::currency::Currency;
use crate::types::v1;

use super::{specifier, Specifier};

impl_hash_id!(Hash256);
impl_hash_id!(BlockID);
impl_hash_id!(SiacoinOutputID);
impl_hash_id!(AttestationID);

impl_hash_id!(SiafundOutputID);
impl_hash_id!(FileContractID);

impl SiafundOutputID {
/// claim_output_id returns the SiacoinOutputID for the claim output of the siafund output
pub fn claim_output_id(&self) -> SiacoinOutputID {
let mut state = Params::new().hash_length(32).to_state();
state.update(self.as_ref());
state.finalize().into()
}

pub fn v2_claim_output_id(&self) -> SiacoinOutputID {
let mut state = Params::new().hash_length(32).to_state();
state.update(b"sia/id/v2siacoinclaimoutput|");
state.update(self.as_ref());
state.finalize().into()
}
}

impl_hash_id!(BlockID);

impl BlockID {
const FOUNDATION_OUTPUT_ID_PREFIX: Specifier = specifier!("foundation");

pub fn foundation_output_id(&self) -> SiacoinOutputID {
let mut state = Params::new().hash_length(32).to_state();
state.update(self.as_ref());
state.update(Self::FOUNDATION_OUTPUT_ID_PREFIX.as_bytes());
state.finalize().into()
}

pub fn miner_output_id(&self, i: usize) -> SiacoinOutputID {
let mut state = Params::new().hash_length(32).to_state();
state.update(self.as_ref());
state.update(&(i as u64).to_le_bytes());
state.finalize().into()
}
}

impl_hash_id!(TransactionID);
impl_hash_id!(AttestationID);

impl TransactionID {
const V2_SIACOIN_OUTPUT_PREFIX: &[u8] = b"sia/id/siacoinoutput|";
const V2_SIAFUND_OUTPUT_PREFIX: &[u8] = b"sia/id/siafundoutput|";
const V2_FILE_CONTRACT_PREFIX: &[u8] = b"sia/id/filecontract|";
const V2_ATTESTATION_PREFIX: &[u8] = b"sia/id/attestation|";

fn derive_v2_child_id<T: From<blake2b_simd::Hash>>(&self, prefix: &[u8], i: usize) -> T {
let mut state = Params::new().hash_length(32).to_state();
state.update(prefix.as_ref());
state.update(self.as_ref());
state.update(&(i as u64).to_le_bytes());
state.finalize().into()
}

/// v2_siacoin_output_id returns the SiacoinOutputID for the i-th siacoin output of the V2 transaction
pub fn v2_siacoin_output_id(&self, i: usize) -> SiacoinOutputID {
self.derive_v2_child_id(Self::V2_SIACOIN_OUTPUT_PREFIX, i)
}

/// v2_siafund_output_id returns the SiafundOutputID for the i-th siafund output of the V2 transaction
pub fn v2_siafund_output_id(&self, i: usize) -> SiafundOutputID {
self.derive_v2_child_id(Self::V2_SIAFUND_OUTPUT_PREFIX, i)
}

/// v2_file_contract_id returns the FileContractID for the i-th file contract of the V2 transaction
pub fn v2_file_contract_id(&self, i: usize) -> FileContractID {
self.derive_v2_child_id(Self::V2_FILE_CONTRACT_PREFIX, i)
}

/// v2_attestation_id returns the AttestationID for the i-th attestation of the V2 transaction
pub fn v2_attestation_id(&self, i: usize) -> AttestationID {
self.derive_v2_child_id(Self::V2_ATTESTATION_PREFIX, i)
}
}

impl_hash_id!(FileContractID);

impl FileContractID {
const PROOF_OUTPUT_ID_PREFIX: Specifier = specifier!("storage proof");
const V2_PROOF_OUTPUT_ID_PREFIX: &'static str = "id/v2filecontractoutput";
const V2_FILE_CONTRACT_RENEWAL_PREFIX: &'static str = "id/v2filecontractrenewal";

fn derive_proof_output_id<T: From<blake2b_simd::Hash>>(&self, valid: bool, i: usize) -> T {
let mut state = Params::new().hash_length(32).to_state();
state.update(Self::PROOF_OUTPUT_ID_PREFIX.as_bytes());
state.update(self.as_ref());
state.update(&(valid as u8).to_le_bytes());
state.update(&(i as u64).to_le_bytes());
state.finalize().into()
}

fn derive_v2_proof_output_id<T: From<blake2b_simd::Hash>>(&self, i: usize) -> T {
let mut state = Params::new().hash_length(32).to_state();
state.update(Self::V2_PROOF_OUTPUT_ID_PREFIX.as_ref());
state.update(self.as_ref());
state.update(&(i as u64).to_le_bytes());
state.finalize().into()
}

/// valid_output_id returns the SiacoinOutputID for the i-th valid output of the contract
pub fn valid_output_id(&self, i: usize) -> SiacoinOutputID {
self.derive_proof_output_id(true, i)
}

/// missed_output_id returns the SiacoinOutputID for the i-th missed output of the contract
pub fn missed_output_id(&self, i: usize) -> SiacoinOutputID {
self.derive_proof_output_id(false, i)
}

/// v2_renter_output_id returns the SiacoinOutputID for the renter output of a V2 file contract
pub fn v2_renter_output_id(&self) -> SiacoinOutputID {
self.derive_v2_proof_output_id(0)
}

/// v2_host_output_id returns the SiacoinOutputID for the host output of a V2 file contract
pub fn v2_host_output_id(&self) -> SiacoinOutputID {
self.derive_v2_proof_output_id(1)
}

/// v2_renewal_id returns the ID of the new contract created by renewing a V2 contract
pub fn v2_renewal_id(&self) -> FileContractID {
let mut state = Params::new().hash_length(32).to_state();
state.update(Self::V2_FILE_CONTRACT_RENEWAL_PREFIX.as_ref());
state.update(self.as_ref());
state.finalize().into()
}
}

#[derive(Debug, PartialEq, SiaEncode, SiaDecode, Serialize, Deserialize)]
pub struct ChainIndex {
Expand Down Expand Up @@ -270,7 +396,9 @@ pub struct StateElement {

#[cfg(test)]
mod tests {
use crate::{address, block_id, public_key, siacoin_id};
use crate::{
address, block_id, contract_id, public_key, siacoin_id, siafund_id, transaction_id,
};

use super::*;

Expand Down Expand Up @@ -353,17 +481,131 @@ mod tests {
],
};

const BINARY_STR: &'static str = "8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c90dc120000000000000000000000000001000000000000000700000000000000cb563bafbb55c90000000000000000000000000000000000000000000000000000000000000000010000000000000001000000000000008fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c000000000000000001000000000000006564323535313900000000000000000020000000000000008fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c010000000000000001000000000000000700000000000000f11318f74d10cf000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
const BINARY_STR: &str = "8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c90dc120000000000000000000000000001000000000000000700000000000000cb563bafbb55c90000000000000000000000000000000000000000000000000000000000000000010000000000000001000000000000008fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c000000000000000001000000000000006564323535313900000000000000000020000000000000008fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c010000000000000001000000000000000700000000000000f11318f74d10cf000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
let mut serialized = Vec::new();
b.encode_v1(&mut serialized).unwrap();
assert_eq!(serialized, hex::decode(BINARY_STR).unwrap());
let deserialized = Block::decode_v1(&mut &serialized[..]).unwrap();
assert_eq!(deserialized, b);

const JSON_STR: &'static str = "{\"parentID\":\"8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c\",\"nonce\":1236112,\"timestamp\":\"1970-01-01T00:00:00Z\",\"minerPayouts\":[{\"value\":\"57234234623612361\",\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"}],\"transactions\":[{\"siacoinInputs\":[{\"parentID\":\"8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c\",\"unlockConditions\":{\"timelock\":0,\"publicKeys\":[\"ed25519:8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c\"],\"signaturesRequired\":1}}],\"siacoinOutputs\":[{\"value\":\"67856467336433871\",\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"}]}]}";
const JSON_STR: &str = "{\"parentID\":\"8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c\",\"nonce\":1236112,\"timestamp\":\"1970-01-01T00:00:00Z\",\"minerPayouts\":[{\"value\":\"57234234623612361\",\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"}],\"transactions\":[{\"siacoinInputs\":[{\"parentID\":\"8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c\",\"unlockConditions\":{\"timelock\":0,\"publicKeys\":[\"ed25519:8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c\"],\"signaturesRequired\":1}}],\"siacoinOutputs\":[{\"value\":\"67856467336433871\",\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"}]}]}";
let serialized = serde_json::to_string(&b).unwrap();
assert_eq!(serialized, JSON_STR);
let deserialized: Block = serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized, b);
}

#[test]
fn test_transaction_derive() {
const TXN_JSON: &str = r#"{"siacoinInputs":[{"parentID":"750d22eff727689d1d8d1c83e513a30bb68ee7f9125a4dafc882459e34c2069d","unlockConditions":{"timelock":0,"publicKeys":["ed25519:800ed6c2760e3e4ba1ff00128585c8cf8fed2e3dc1e3da1eb92d49f405bd6360"],"signaturesRequired":6312611591377486220}}],"siacoinOutputs":[{"value":"890415399000000000000000000000000","address":"480a064b5fca13002a7fe575845154bbf0b3af4cc4f147cbed387d43cce3568ae2497366eaa7"}],"fileContracts":[{"filesize":0,"fileMerkleRoot":"0000000000000000000000000000000000000000000000000000000000000000","windowStart":10536451586783908586,"windowEnd":9324702155635244357,"payout":"0","validProofOutputs":[{"value":"1933513214000000000000000000000000","address":"944524fff2c49c401e748db37cfda7569fa6df35b704fe716394f2ac3f40ce87b4506e9906f0"}],"missedProofOutputs":[{"value":"2469287901000000000000000000000000","address":"1df67838262d7109ffcd9018f183b1eb33f05659a274b89ea6b52ff3617d34a770e9dd071d2e"}],"unlockHash":"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69","revisionNumber":9657412421282982780}],"fileContractRevisions":[{"parentID":"e4e26d93771d3bbb3d9dd306105d77cfb3a6254d1cc3495903af6e013442c63c","unlockConditions":{"timelock":0,"publicKeys":["ed25519:e6b9cde4eb058f8ecbb083d99779cb0f6d518d5386f019af6ead09fa52de8567"],"signaturesRequired":206644730660526450},"revisionNumber":10595710523108536025,"filesize":0,"fileMerkleRoot":"0000000000000000000000000000000000000000000000000000000000000000","windowStart":4348934140507359445,"windowEnd":14012366839994454386,"validProofOutputs":[{"value":"2435858510000000000000000000000000","address":"543bc0eda69f728d0a0fbce08e5bfc5ed7b961300e0af226949e135f7d12e32f0544e5262d6f"}],"missedProofOutputs":[{"value":"880343701000000000000000000000000","address":"7b7f9aee981fe0d93bb3f49c6233cf847ebdd39d7dc5253f7fc330df2167073b35f035703237"}],"unlockHash":"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69"}],"storageProofs":[{"parentID":"c0b9e98c9e03a2740c75d673871c1ee91f36d1bb329ff3ddbf1dfa8c6e1a64eb","leaf":"b78fa521dc62d9ced82bc3b61e0aa5a5c221d6cca5db63d94c9879543fb98c0a971094a89cd4408487ae32902248d321b545f9a051729aa0bb1725b848e3d453","proof":["fe08c0a061475e7e5dec19e717cf98792fa7b555d0b5d3540a05db09f59ab8de"]}],"minerFees":["241119475000000000000000000000000"],"arbitraryData":["2shzIHEUJYwuNHz6c/gPz+aTEWZRTpDTmemX9yYAKlY="],"signatures":[{"parentID":"06d1fca03c5ddd9b09116db1b97c5451f7dc792b05362969f83e3e8dc1007f46","publicKeyIndex":6088345341283457116,"timelock":2014247885072555224,"coveredFields":{"wholeTransaction":true},"signature":"2XNEKGZrl9RhMa2JmGsvcmqQWAIX/uxtMwLnPI6VJPcXqub6qYIuoAThYp9NAwadk+1GG6CXC66g4rOjFYuNSA=="}]}"#;

const EXPECTED_TRANSACTION_ID: TransactionID =
transaction_id!("71a10d363f4af09c3fbce499b725067b0b19afe2bc9a8236704e85256f3244a6");
const EXPECTED_SIACOIN_OUTPUT_ID: SiacoinOutputID =
siacoin_id!("ea315efdd5914c54e8082d0de90b5afa9d4b92103d60661ec86b2a095413d836");
const EXPECTED_SIAFUND_OUTPUT_ID: SiafundOutputID =
siafund_id!("a8190ea7b4d41e08f45f27653b882faf8ff9fd57bb098d7022f105ef142279ec");
const EXPECTED_FILE_CONTRACT_ID: FileContractID =
contract_id!("ff7102bb111a64c7ff8a3cd68dbc962a03a8943065c3852a359662c8935fa979");

let txn: v1::Transaction =
serde_json::from_str(TXN_JSON).expect("transaction to deserialize");

assert_eq!(txn.id(), EXPECTED_TRANSACTION_ID, "transaction id");

assert_eq!(
txn.siacoin_output_id(678569214627704587),
EXPECTED_SIACOIN_OUTPUT_ID,
"siacoin output id"
);

assert_eq!(
txn.siafund_output_id(8940170890223196046),
EXPECTED_SIAFUND_OUTPUT_ID,
"siafund output id"
);

assert_eq!(
txn.file_contract_id(3470616158951613631),
EXPECTED_FILE_CONTRACT_ID,
"file contract id"
);
}

#[test]
fn test_transaction_id_v2_derive() {
const EXPECTED_V2_SIACOIN_OUTPUT_ID: SiacoinOutputID =
siacoin_id!("f74e0d8eae89ec820184c9bacfcad0181c781c02020f8a3fcbc82fd4ebf2fcf0");
const EXPECTED_V2_SIAFUND_OUTPUT_ID: SiafundOutputID =
siafund_id!("f7d9ad77bfe9a102ef9590f97024f3aa8f54877d10447c128b52d5ca18cca983");
const EXPECTED_V2_FILE_CONTRACT_ID: FileContractID =
contract_id!("c67764bc06df3dd933e0d4e93c6f7cbe5b56670d1baae156b578d417f08e65cf");

let txn_id =
transaction_id!("168ecf3133ae713c26f90fe1790fb7536f12cc2a492985627856b77c6ad99070");

assert_eq!(
txn_id.v2_siacoin_output_id(3543556734851495409),
EXPECTED_V2_SIACOIN_OUTPUT_ID,
"v2 siacoin output id"
);

assert_eq!(
txn_id.v2_siafund_output_id(4957302981402025980),
EXPECTED_V2_SIAFUND_OUTPUT_ID,
"v2 siafund output id"
);

assert_eq!(
txn_id.v2_file_contract_id(5375460735837768427),
EXPECTED_V2_FILE_CONTRACT_ID,
"v2 file contract id"
);
}

#[test]
fn test_block_id_derive() {
const EXPECTED_FOUNDATION_OUTPUT_ID: SiacoinOutputID =
siacoin_id!("159e2c4159a112ea9a70242d541a26f49fce41b6126f9105eab9b68dba4cfafb");
const EXPECTED_MINER_OUTPUT_ID: SiacoinOutputID =
siacoin_id!("69e68779991392663d808276e6661d94628632354e258d8ab6724de1d9ca6208");

let block_id =
block_id!("c56d879b07b27fab3bdd06b833dbd1ad7eb167058851f543a517308b634a80a1");

assert_eq!(
block_id.foundation_output_id(),
EXPECTED_FOUNDATION_OUTPUT_ID,
"foundation output id"
);

assert_eq!(
block_id.miner_output_id(3072616177397065894),
EXPECTED_MINER_OUTPUT_ID,
"miner output id"
);
}

#[test]
fn test_siafund_output_id_derive() {
const EXPECTED_CLAIM_ID: SiacoinOutputID =
siacoin_id!("8eec57722c2ac040e34322ba77cb6b488ac8081f856d93bea1bf1bef42aeaabb");
const EXPECTED_V2_CLAIM_ID: SiacoinOutputID =
siacoin_id!("b949006c65c70b5973da46cc783981d701dd854316e7efb1947c0b5f2fdc8db4");

let siafund_output_id =
siafund_id!("58ea19fd87ae5e10f928035e1021c3d9ee091fb3c0bbd5a1a6af41eea12e0f85");

assert_eq!(
siafund_output_id.claim_output_id(),
EXPECTED_CLAIM_ID,
"claim output id"
);

assert_eq!(
siafund_output_id.v2_claim_output_id(),
EXPECTED_V2_CLAIM_ID,
"v2 claim output id"
);
}
}
2 changes: 1 addition & 1 deletion sia/src/types/spendpolicy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -687,7 +687,7 @@ mod tests {
// json
let serialized = serde_json::to_string(&tc.policy).unwrap();
assert_eq!(serialized, tc.json, "json serialization failed");
let deserialized: SatisfiedPolicy = serde_json::from_str(&tc.json).unwrap();
let deserialized: SatisfiedPolicy = serde_json::from_str(tc.json).unwrap();
assert_eq!(deserialized, tc.policy, "json deserialization failed");
}
}
Expand Down
Loading

0 comments on commit 1480562

Please sign in to comment.