Advance forge rollout, Ethereum rails, and NBC sources
This commit is contained in:
parent
be26313225
commit
7d84510eac
88 changed files with 11230 additions and 302 deletions
12
crates/ec-eth/Cargo.toml
Normal file
12
crates/ec-eth/Cargo.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "ec-eth"
|
||||
version = "0.0.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
alloy-primitives = "1.5.7"
|
||||
alloy-sol-types = "1.5.7"
|
||||
blake3 = "1"
|
||||
ec-core = { path = "../ec-core" }
|
||||
hex = "0.4"
|
||||
642
crates/ec-eth/src/lib.rs
Normal file
642
crates/ec-eth/src/lib.rs
Normal file
|
|
@ -0,0 +1,642 @@
|
|||
//! Ethereum-compatible representations and commitments for every.channel core types.
|
||||
|
||||
use alloy_primitives::{keccak256, B256};
|
||||
use alloy_sol_types::{eip712_domain, sol, Eip712Domain, SolStruct, SolValue};
|
||||
use ec_core::{
|
||||
BroadcastId, ChainCommitment, ChunkId, Manifest, ManifestBody, ManifestSignature,
|
||||
ManifestVariant, SourceId, StreamControlAnnouncement, StreamDescriptor, StreamKey,
|
||||
StreamMetadata, StreamTransportDescriptor,
|
||||
};
|
||||
use std::fmt;
|
||||
|
||||
pub const ETHEREUM_CHAIN: &str = "ethereum";
|
||||
pub const SCHEME_STREAM_ID_KECCAK: &str = "stream-id-keccak256-v1";
|
||||
pub const SCHEME_STREAM_DESCRIPTOR_ABI: &str = "stream-descriptor-abi-keccak256-v1";
|
||||
pub const SCHEME_CONTROL_ANNOUNCEMENT_ABI: &str = "control-announcement-abi-keccak256-v1";
|
||||
pub const SCHEME_MANIFEST_DATA_ROOT: &str = "manifest-data-merkle-keccak256-v1";
|
||||
pub const SCHEME_MANIFEST_BODY_ABI: &str = "manifest-body-abi-keccak256-v1";
|
||||
pub const SCHEME_MANIFEST_ENVELOPE_ABI: &str = "manifest-envelope-abi-keccak256-v1";
|
||||
pub const ETH_MANIFEST_SIG_ALG: &str = "secp256k1-eip712-manifest-body-v1";
|
||||
|
||||
sol! {
|
||||
struct EthStreamMetadata {
|
||||
string key;
|
||||
string value;
|
||||
}
|
||||
|
||||
struct EthBroadcastId {
|
||||
string standard;
|
||||
bool hasTransportStreamId;
|
||||
uint16 transportStreamId;
|
||||
bool hasProgramNumber;
|
||||
uint16 programNumber;
|
||||
string virtualChannel;
|
||||
string callsign;
|
||||
string region;
|
||||
string frequency;
|
||||
}
|
||||
|
||||
struct EthSourceId {
|
||||
string kind;
|
||||
string deviceId;
|
||||
string channel;
|
||||
}
|
||||
|
||||
struct EthStreamKey {
|
||||
uint16 version;
|
||||
bool hasBroadcast;
|
||||
EthBroadcastId broadcast;
|
||||
bool hasSource;
|
||||
EthSourceId source;
|
||||
string profile;
|
||||
string variant;
|
||||
}
|
||||
|
||||
struct EthStreamDescriptor {
|
||||
string id;
|
||||
string title;
|
||||
string number;
|
||||
string source;
|
||||
EthStreamMetadata[] metadata;
|
||||
}
|
||||
|
||||
struct EthStreamTransportDescriptor {
|
||||
uint8 kind;
|
||||
string url;
|
||||
string endpoint;
|
||||
string broadcastName;
|
||||
string trackName;
|
||||
}
|
||||
|
||||
struct EthStreamControlAnnouncement {
|
||||
EthStreamDescriptor stream;
|
||||
EthStreamTransportDescriptor[] transports;
|
||||
uint64 updatedUnixMs;
|
||||
uint64 ttlMs;
|
||||
}
|
||||
|
||||
struct EthChunkId {
|
||||
string streamId;
|
||||
string epochId;
|
||||
uint64 chunkIndex;
|
||||
bytes32 chunkHash;
|
||||
}
|
||||
|
||||
struct EthManifestVariant {
|
||||
string variantId;
|
||||
string streamId;
|
||||
uint64 chunkStartIndex;
|
||||
uint64 totalChunks;
|
||||
bytes32 merkleRoot;
|
||||
bytes32[] chunkHashes;
|
||||
EthStreamMetadata[] metadata;
|
||||
}
|
||||
|
||||
struct EthManifestBody {
|
||||
string streamId;
|
||||
string epochId;
|
||||
uint64 chunkDurationMs;
|
||||
uint64 totalChunks;
|
||||
uint64 chunkStartIndex;
|
||||
string encoderProfileId;
|
||||
bytes32 merkleRoot;
|
||||
uint64 createdUnixMs;
|
||||
EthStreamMetadata[] metadata;
|
||||
bytes32[] chunkHashes;
|
||||
EthManifestVariant[] variants;
|
||||
}
|
||||
|
||||
struct EthManifestSignature {
|
||||
string signerId;
|
||||
string alg;
|
||||
bytes signature;
|
||||
}
|
||||
|
||||
struct EthManifest {
|
||||
EthManifestBody body;
|
||||
bytes32 manifestId;
|
||||
EthManifestSignature[] signatures;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum EthCommitmentError {
|
||||
Empty,
|
||||
InvalidHex(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for EthCommitmentError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
EthCommitmentError::Empty => write!(f, "no hashes supplied"),
|
||||
EthCommitmentError::InvalidHex(value) => {
|
||||
write!(f, "invalid 32-byte hex value: {value}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for EthCommitmentError {}
|
||||
|
||||
fn commitment(scheme: &str, digest: B256) -> ChainCommitment {
|
||||
ChainCommitment {
|
||||
chain: ETHEREUM_CHAIN.to_string(),
|
||||
scheme: scheme.to_string(),
|
||||
digest: format!("0x{}", hex::encode(digest)),
|
||||
}
|
||||
}
|
||||
|
||||
fn abi_commitment<T: SolValue>(scheme: &str, value: &T) -> ChainCommitment {
|
||||
commitment(scheme, keccak256(value.abi_encode()))
|
||||
}
|
||||
|
||||
fn parse_b256(value: &str) -> Result<B256, EthCommitmentError> {
|
||||
let trimmed = value.trim().strip_prefix("0x").unwrap_or(value.trim());
|
||||
let bytes =
|
||||
hex::decode(trimmed).map_err(|_| EthCommitmentError::InvalidHex(value.to_string()))?;
|
||||
if bytes.len() != 32 {
|
||||
return Err(EthCommitmentError::InvalidHex(value.to_string()));
|
||||
}
|
||||
let mut out = [0u8; 32];
|
||||
out.copy_from_slice(&bytes);
|
||||
Ok(B256::from(out))
|
||||
}
|
||||
|
||||
fn parse_bytes(value: &str) -> Vec<u8> {
|
||||
let trimmed = value.trim().strip_prefix("0x").unwrap_or(value.trim());
|
||||
hex::decode(trimmed).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn manifest_eip712_domain() -> Eip712Domain {
|
||||
eip712_domain! {
|
||||
name: "every.channel",
|
||||
version: "1",
|
||||
}
|
||||
}
|
||||
|
||||
fn eth_metadata(items: &[StreamMetadata]) -> Vec<EthStreamMetadata> {
|
||||
items
|
||||
.iter()
|
||||
.map(|item| EthStreamMetadata {
|
||||
key: item.key.clone(),
|
||||
value: item.value.clone(),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn eth_broadcast_id(value: &BroadcastId) -> EthBroadcastId {
|
||||
EthBroadcastId {
|
||||
standard: value.standard.clone(),
|
||||
hasTransportStreamId: value.transport_stream_id.is_some(),
|
||||
transportStreamId: value.transport_stream_id.unwrap_or_default(),
|
||||
hasProgramNumber: value.program_number.is_some(),
|
||||
programNumber: value.program_number.unwrap_or_default(),
|
||||
virtualChannel: value.virtual_channel.clone().unwrap_or_default(),
|
||||
callsign: value.callsign.clone().unwrap_or_default(),
|
||||
region: value.region.clone().unwrap_or_default(),
|
||||
frequency: value.frequency.clone().unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eth_source_id(value: &SourceId) -> EthSourceId {
|
||||
EthSourceId {
|
||||
kind: value.kind.clone(),
|
||||
deviceId: value.device_id.clone().unwrap_or_default(),
|
||||
channel: value.channel.clone().unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eth_stream_key(value: &StreamKey) -> EthStreamKey {
|
||||
EthStreamKey {
|
||||
version: value.version,
|
||||
hasBroadcast: value.broadcast.is_some(),
|
||||
broadcast: value
|
||||
.broadcast
|
||||
.as_ref()
|
||||
.map(eth_broadcast_id)
|
||||
.unwrap_or_else(|| EthBroadcastId {
|
||||
standard: String::new(),
|
||||
hasTransportStreamId: false,
|
||||
transportStreamId: 0,
|
||||
hasProgramNumber: false,
|
||||
programNumber: 0,
|
||||
virtualChannel: String::new(),
|
||||
callsign: String::new(),
|
||||
region: String::new(),
|
||||
frequency: String::new(),
|
||||
}),
|
||||
hasSource: value.source.is_some(),
|
||||
source: value
|
||||
.source
|
||||
.as_ref()
|
||||
.map(eth_source_id)
|
||||
.unwrap_or_else(|| EthSourceId {
|
||||
kind: String::new(),
|
||||
deviceId: String::new(),
|
||||
channel: String::new(),
|
||||
}),
|
||||
profile: value.profile.clone().unwrap_or_default(),
|
||||
variant: value.variant.clone().unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eth_stream_descriptor(value: &StreamDescriptor) -> EthStreamDescriptor {
|
||||
EthStreamDescriptor {
|
||||
id: value.id.0.clone(),
|
||||
title: value.title.clone(),
|
||||
number: value.number.clone().unwrap_or_default(),
|
||||
source: value.source.clone(),
|
||||
metadata: eth_metadata(&value.metadata),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eth_stream_transport_descriptor(
|
||||
value: &StreamTransportDescriptor,
|
||||
) -> EthStreamTransportDescriptor {
|
||||
match value {
|
||||
StreamTransportDescriptor::RelayMoq {
|
||||
url,
|
||||
broadcast_name,
|
||||
track_name,
|
||||
} => EthStreamTransportDescriptor {
|
||||
kind: 0,
|
||||
url: url.clone(),
|
||||
endpoint: String::new(),
|
||||
broadcastName: broadcast_name.clone(),
|
||||
trackName: track_name.clone(),
|
||||
},
|
||||
StreamTransportDescriptor::IrohDirect {
|
||||
endpoint,
|
||||
broadcast_name,
|
||||
track_name,
|
||||
} => EthStreamTransportDescriptor {
|
||||
kind: 1,
|
||||
url: String::new(),
|
||||
endpoint: endpoint.clone(),
|
||||
broadcastName: broadcast_name.clone(),
|
||||
trackName: track_name.clone(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eth_stream_control_announcement(
|
||||
value: &StreamControlAnnouncement,
|
||||
) -> EthStreamControlAnnouncement {
|
||||
EthStreamControlAnnouncement {
|
||||
stream: eth_stream_descriptor(&value.stream),
|
||||
transports: value
|
||||
.transports
|
||||
.iter()
|
||||
.map(eth_stream_transport_descriptor)
|
||||
.collect(),
|
||||
updatedUnixMs: value.updated_unix_ms,
|
||||
ttlMs: value.ttl_ms,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eth_chunk_id(value: &ChunkId) -> Result<EthChunkId, EthCommitmentError> {
|
||||
Ok(EthChunkId {
|
||||
streamId: value.stream_id.0.clone(),
|
||||
epochId: value.epoch_id.clone(),
|
||||
chunkIndex: value.chunk_index,
|
||||
chunkHash: parse_b256(&value.chunk_hash)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn eth_manifest_variant(
|
||||
value: &ManifestVariant,
|
||||
) -> Result<EthManifestVariant, EthCommitmentError> {
|
||||
Ok(EthManifestVariant {
|
||||
variantId: value.variant_id.clone(),
|
||||
streamId: value.stream_id.0.clone(),
|
||||
chunkStartIndex: value.chunk_start_index,
|
||||
totalChunks: value.total_chunks,
|
||||
merkleRoot: parse_b256(&value.merkle_root)?,
|
||||
chunkHashes: value
|
||||
.chunk_hashes
|
||||
.iter()
|
||||
.map(|hash| parse_b256(hash))
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
metadata: eth_metadata(&value.metadata),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn eth_manifest_body(value: &ManifestBody) -> Result<EthManifestBody, EthCommitmentError> {
|
||||
let variants = value
|
||||
.variants
|
||||
.as_ref()
|
||||
.map(|variants| {
|
||||
variants
|
||||
.iter()
|
||||
.map(eth_manifest_variant)
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
})
|
||||
.transpose()?
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok(EthManifestBody {
|
||||
streamId: value.stream_id.0.clone(),
|
||||
epochId: value.epoch_id.clone(),
|
||||
chunkDurationMs: value.chunk_duration_ms,
|
||||
totalChunks: value.total_chunks,
|
||||
chunkStartIndex: value.chunk_start_index,
|
||||
encoderProfileId: value.encoder_profile_id.clone(),
|
||||
merkleRoot: parse_b256(&value.merkle_root)?,
|
||||
createdUnixMs: value.created_unix_ms,
|
||||
metadata: eth_metadata(&value.metadata),
|
||||
chunkHashes: value
|
||||
.chunk_hashes
|
||||
.iter()
|
||||
.map(|hash| parse_b256(hash))
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
variants,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn manifest_body_eip712_signing_hash(value: &ManifestBody) -> Result<B256, EthCommitmentError> {
|
||||
Ok(eth_manifest_body(value)?.eip712_signing_hash(&manifest_eip712_domain()))
|
||||
}
|
||||
|
||||
pub fn eth_manifest_signature(value: &ManifestSignature) -> EthManifestSignature {
|
||||
EthManifestSignature {
|
||||
signerId: value.signer_id.clone(),
|
||||
alg: value.alg.clone(),
|
||||
signature: parse_bytes(&value.signature).into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eth_manifest(value: &Manifest) -> Result<EthManifest, EthCommitmentError> {
|
||||
Ok(EthManifest {
|
||||
body: eth_manifest_body(&value.body)?,
|
||||
manifestId: parse_b256(&value.manifest_id)?,
|
||||
signatures: value
|
||||
.signatures
|
||||
.iter()
|
||||
.map(eth_manifest_signature)
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
|
||||
fn keccak_merkle_root(leaves: &[B256]) -> Result<B256, EthCommitmentError> {
|
||||
if leaves.is_empty() {
|
||||
return Err(EthCommitmentError::Empty);
|
||||
}
|
||||
let mut nodes = leaves.to_vec();
|
||||
while nodes.len() > 1 {
|
||||
if nodes.len() % 2 == 1 {
|
||||
if let Some(last) = nodes.last().copied() {
|
||||
nodes.push(last);
|
||||
}
|
||||
}
|
||||
let mut parents = Vec::with_capacity(nodes.len() / 2);
|
||||
for pair in nodes.chunks(2) {
|
||||
let mut merged = [0u8; 64];
|
||||
merged[..32].copy_from_slice(pair[0].as_slice());
|
||||
merged[32..].copy_from_slice(pair[1].as_slice());
|
||||
parents.push(keccak256(merged));
|
||||
}
|
||||
nodes = parents;
|
||||
}
|
||||
Ok(nodes[0])
|
||||
}
|
||||
|
||||
pub fn ethereum_merkle_root_from_hashes(hashes: &[String]) -> Result<String, EthCommitmentError> {
|
||||
let leaves = hashes
|
||||
.iter()
|
||||
.map(|hash| parse_b256(hash))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
Ok(format!("0x{}", hex::encode(keccak_merkle_root(&leaves)?)))
|
||||
}
|
||||
|
||||
pub fn ethereum_merkle_proof_for_index(
|
||||
hashes: &[String],
|
||||
index: usize,
|
||||
) -> Result<Vec<String>, EthCommitmentError> {
|
||||
if hashes.is_empty() {
|
||||
return Err(EthCommitmentError::Empty);
|
||||
}
|
||||
if index >= hashes.len() {
|
||||
return Err(EthCommitmentError::InvalidHex(format!(
|
||||
"index {index} out of bounds"
|
||||
)));
|
||||
}
|
||||
|
||||
let mut nodes = hashes
|
||||
.iter()
|
||||
.map(|hash| parse_b256(hash))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let mut proof = Vec::new();
|
||||
let mut pos = index;
|
||||
while nodes.len() > 1 {
|
||||
if nodes.len() % 2 == 1 {
|
||||
if let Some(last) = nodes.last().copied() {
|
||||
nodes.push(last);
|
||||
}
|
||||
}
|
||||
let sibling_index = if pos % 2 == 0 { pos + 1 } else { pos - 1 };
|
||||
let sibling = nodes[sibling_index];
|
||||
proof.push(format!("0x{}", hex::encode(sibling)));
|
||||
|
||||
let mut parents = Vec::with_capacity(nodes.len() / 2);
|
||||
for pair in nodes.chunks(2) {
|
||||
let mut merged = [0u8; 64];
|
||||
merged[..32].copy_from_slice(pair[0].as_slice());
|
||||
merged[32..].copy_from_slice(pair[1].as_slice());
|
||||
parents.push(keccak256(merged));
|
||||
}
|
||||
nodes = parents;
|
||||
pos /= 2;
|
||||
}
|
||||
|
||||
Ok(proof)
|
||||
}
|
||||
|
||||
pub fn verify_ethereum_merkle_proof(
|
||||
leaf_hash: &str,
|
||||
mut index: usize,
|
||||
branch: &[String],
|
||||
expected_root: &str,
|
||||
) -> bool {
|
||||
let Ok(mut acc) = parse_b256(leaf_hash) else {
|
||||
return false;
|
||||
};
|
||||
for sibling_hex in branch {
|
||||
let Ok(sibling) = parse_b256(sibling_hex) else {
|
||||
return false;
|
||||
};
|
||||
let (left, right) = if index % 2 == 0 {
|
||||
(acc, sibling)
|
||||
} else {
|
||||
(sibling, acc)
|
||||
};
|
||||
let mut merged = [0u8; 64];
|
||||
merged[..32].copy_from_slice(left.as_slice());
|
||||
merged[32..].copy_from_slice(right.as_slice());
|
||||
acc = keccak256(merged);
|
||||
index /= 2;
|
||||
}
|
||||
match parse_b256(expected_root) {
|
||||
Ok(root) => acc == root,
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stream_id_commitment(stream_id: &str) -> ChainCommitment {
|
||||
commitment(SCHEME_STREAM_ID_KECCAK, keccak256(stream_id.as_bytes()))
|
||||
}
|
||||
|
||||
pub fn broadcast_id_commitment(value: &BroadcastId) -> ChainCommitment {
|
||||
abi_commitment("broadcast-id-abi-keccak256-v1", ð_broadcast_id(value))
|
||||
}
|
||||
|
||||
pub fn stream_key_commitment(value: &StreamKey) -> ChainCommitment {
|
||||
abi_commitment("stream-key-abi-keccak256-v1", ð_stream_key(value))
|
||||
}
|
||||
|
||||
pub fn stream_descriptor_commitments(value: &StreamDescriptor) -> Vec<ChainCommitment> {
|
||||
vec![
|
||||
stream_id_commitment(&value.id.0),
|
||||
abi_commitment(SCHEME_STREAM_DESCRIPTOR_ABI, ð_stream_descriptor(value)),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn control_announcement_commitments(value: &StreamControlAnnouncement) -> Vec<ChainCommitment> {
|
||||
vec![abi_commitment(
|
||||
SCHEME_CONTROL_ANNOUNCEMENT_ABI,
|
||||
ð_stream_control_announcement(value),
|
||||
)]
|
||||
}
|
||||
|
||||
fn manifest_data_root_commitment(
|
||||
body: &ManifestBody,
|
||||
) -> Result<ChainCommitment, EthCommitmentError> {
|
||||
let root = if let Some(variants) = body
|
||||
.variants
|
||||
.as_ref()
|
||||
.filter(|variants| !variants.is_empty())
|
||||
{
|
||||
let roots = variants
|
||||
.iter()
|
||||
.map(|variant| variant.merkle_root.clone())
|
||||
.collect::<Vec<_>>();
|
||||
ethereum_merkle_root_from_hashes(&roots)?
|
||||
} else {
|
||||
ethereum_merkle_root_from_hashes(&body.chunk_hashes)?
|
||||
};
|
||||
Ok(ChainCommitment {
|
||||
chain: ETHEREUM_CHAIN.to_string(),
|
||||
scheme: SCHEME_MANIFEST_DATA_ROOT.to_string(),
|
||||
digest: root,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn manifest_commitments(value: &Manifest) -> Result<Vec<ChainCommitment>, EthCommitmentError> {
|
||||
let eth_body = eth_manifest_body(&value.body)?;
|
||||
let eth_manifest = eth_manifest(value)?;
|
||||
Ok(vec![
|
||||
manifest_data_root_commitment(&value.body)?,
|
||||
abi_commitment(SCHEME_MANIFEST_BODY_ABI, ð_body),
|
||||
abi_commitment(SCHEME_MANIFEST_ENVELOPE_ABI, ð_manifest),
|
||||
])
|
||||
}
|
||||
|
||||
pub fn manifest_commitments_match(value: &Manifest) -> Result<bool, EthCommitmentError> {
|
||||
let present = value
|
||||
.commitments
|
||||
.iter()
|
||||
.filter(|item| item.chain == ETHEREUM_CHAIN)
|
||||
.collect::<Vec<_>>();
|
||||
if present.is_empty() {
|
||||
return Ok(true);
|
||||
}
|
||||
let expected = manifest_commitments(value)?;
|
||||
Ok(present.into_iter().all(|actual| expected.contains(actual)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use ec_core::{ManifestBody, StreamId};
|
||||
|
||||
fn sample_body() -> ManifestBody {
|
||||
let chunk_hashes = vec![
|
||||
blake3::hash(b"chunk0").to_hex().to_string(),
|
||||
blake3::hash(b"chunk1").to_hex().to_string(),
|
||||
];
|
||||
let merkle_root = ec_core::merkle_root_from_hashes(&chunk_hashes).unwrap();
|
||||
ManifestBody {
|
||||
stream_id: StreamId("ec/stream/v1/broadcast/atsc/tsid-42/program-3".to_string()),
|
||||
epoch_id: "epoch-1".to_string(),
|
||||
chunk_duration_ms: 2000,
|
||||
total_chunks: 2,
|
||||
chunk_start_index: 10,
|
||||
encoder_profile_id: "deterministic-h264-aac".to_string(),
|
||||
merkle_root,
|
||||
created_unix_ms: 1234,
|
||||
metadata: vec![StreamMetadata {
|
||||
key: "callsign".to_string(),
|
||||
value: "KCBS".to_string(),
|
||||
}],
|
||||
chunk_hashes,
|
||||
variants: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keccak_merkle_root_and_proof_roundtrip() {
|
||||
let body = sample_body();
|
||||
let root = ethereum_merkle_root_from_hashes(&body.chunk_hashes).unwrap();
|
||||
let proof = ethereum_merkle_proof_for_index(&body.chunk_hashes, 1).unwrap();
|
||||
assert!(verify_ethereum_merkle_proof(
|
||||
&body.chunk_hashes[1],
|
||||
1,
|
||||
&proof,
|
||||
&root
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn manifest_commitments_are_stable_and_match_present_values() {
|
||||
let body = sample_body();
|
||||
let manifest_id = body.manifest_id().unwrap();
|
||||
let mut manifest = Manifest {
|
||||
body,
|
||||
manifest_id,
|
||||
signatures: Vec::new(),
|
||||
commitments: Vec::new(),
|
||||
};
|
||||
let commitments = manifest_commitments(&manifest).unwrap();
|
||||
assert_eq!(commitments.len(), 3);
|
||||
manifest.commitments = commitments.clone();
|
||||
assert!(manifest_commitments_match(&manifest).unwrap());
|
||||
manifest.commitments[0].digest = "0xdeadbeef".to_string();
|
||||
assert!(!manifest_commitments_match(&manifest).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn manifest_body_eip712_hash_is_stable() {
|
||||
let body = sample_body();
|
||||
let h1 = manifest_body_eip712_signing_hash(&body).unwrap();
|
||||
let h2 = manifest_body_eip712_signing_hash(&body).unwrap();
|
||||
assert_eq!(h1, h2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stream_descriptor_commitments_include_stream_id_and_descriptor_hashes() {
|
||||
let descriptor = StreamDescriptor {
|
||||
id: StreamId("ec/stream/v1/source/test/device-a/channel-b".to_string()),
|
||||
title: "Test".to_string(),
|
||||
number: Some("2.1".to_string()),
|
||||
source: "control".to_string(),
|
||||
metadata: vec![StreamMetadata {
|
||||
key: "broadcast".to_string(),
|
||||
value: "la-nbc".to_string(),
|
||||
}],
|
||||
commitments: Vec::new(),
|
||||
};
|
||||
let commitments = stream_descriptor_commitments(&descriptor);
|
||||
assert_eq!(commitments.len(), 2);
|
||||
assert_eq!(commitments[0].scheme, SCHEME_STREAM_ID_KECCAK);
|
||||
assert_eq!(commitments[1].scheme, SCHEME_STREAM_DESCRIPTOR_ABI);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue