Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions pkg/tbtc/signer/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pkg/tbtc/signer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ serde_json = { version = "1.0", features = ["raw_value"] }
sha2 = "0.10"
hex = "0.4"
thiserror = "2.0"
hkdf = { version = "=0.12.4", default-features = false }
frost-secp256k1-tr = "=3.0.0"
k256 = { version = "=0.13.4", default-features = false, features = ["arithmetic", "ecdh"] }
# Direct, version-matched access to aggregate_custom + CheaterDetection (the
# frost-secp256k1-tr aggregate wrappers hardcode FirstCheater). Already a
# transitive dependency via frost-secp256k1-tr, pinned to the same 3.0.0.
Expand Down
21 changes: 21 additions & 0 deletions pkg/tbtc/signer/include/frost_tbtc.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,27 @@ TbtcSignerResult frost_tbtc_dkg_part2(const uint8_t* request_ptr, size_t request
TbtcSignerResult frost_tbtc_dkg_part3(const uint8_t* request_ptr, size_t request_len);
TbtcSignerResult frost_tbtc_persist_distributed_dkg_key_package(const uint8_t* request_ptr, size_t request_len);
TbtcSignerResult frost_tbtc_retire_distributed_dkg_key_packages(const uint8_t* request_ptr, size_t request_len);
/*
* Offline-authorized disaster-recovery protocol. Part1 deltas and Part2
* sigmas cross this ABI only as native AEAD ciphertexts under an
* authority-signed endpoint roster; the host transport must still preserve
* authenticated sender identity and delivery. Install validates the exact
* context/helper set and writes the reconstructed share directly to the
* authorization-bound fresh durable store; no KeyPackage is returned across
* this ABI. Finish wipes only the live key cache; Begin can rederive the same
* store-bound key until authorization expiry.
*/
TbtcSignerResult frost_tbtc_begin_share_repair_session(
const uint8_t* request_ptr,
size_t request_len
);
TbtcSignerResult frost_tbtc_finish_share_repair_session(
const uint8_t* request_ptr,
size_t request_len
);
TbtcSignerResult frost_tbtc_share_repair_part1(const uint8_t* request_ptr, size_t request_len);
TbtcSignerResult frost_tbtc_share_repair_part2(const uint8_t* request_ptr, size_t request_len);
TbtcSignerResult frost_tbtc_install_repaired_share(const uint8_t* request_ptr, size_t request_len);

TbtcSignerResult frost_tbtc_new_signing_package(const uint8_t* request_ptr, size_t request_len);
TbtcSignerResult frost_tbtc_build_taproot_tx(const uint8_t* request_ptr, size_t request_len);
Expand Down
8 changes: 4 additions & 4 deletions pkg/tbtc/signer/scripts/formal/run_tla_models.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ TLA_TOOLS_VERSION="${TLA_TOOLS_VERSION:-v1.8.0}"
TLA_TOOLS_JAR="${TLA_TOOLS_JAR:-/tmp/tla2tools-${TLA_TOOLS_VERSION}.jar}"
TLA_TOOLS_URL="${TLA_TOOLS_URL:-https://github.com/tlaplus/tlaplus/releases/download/${TLA_TOOLS_VERSION}/tla2tools.jar}"
# Pin the SHA-256 of the upstream tla2tools.jar (github.com/tlaplus/tlaplus
# release v1.8.0). Re-pin this when the upstream release asset is rebuilt and the
# download-verification gate below reports a mismatch, after confirming the new
# jar comes from the official release URL.
TLA_TOOLS_SHA256="${TLA_TOOLS_SHA256:-cc4803dce2a8ffaf0f5920a9dc39df4b5ee34ab4cb53fb58ac557277a7e516b3}"
# rolling release v1.8.0 asset). Upstream may delete and rebuild this asset from
# master. Re-pin only after confirming the replacement digest in GitHub's
# official release metadata; the gate below must continue to fail closed.
TLA_TOOLS_SHA256="${TLA_TOOLS_SHA256:-ab323b79802aedc3203b3f9af37c6aca3ed43f4e0225b36f2aa77b26de46c05f}"

if ! command -v java >/dev/null 2>&1; then
echo "java is required to run TLC model checks" >&2
Expand Down
170 changes: 170 additions & 0 deletions pkg/tbtc/signer/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,161 @@ pub struct RetireDistributedDkgKeyPackagesResult {
pub retired_key_package_count: u16,
}

/// Offline-authority authorization for one bounded repair of one participant
/// share. The signature covers every field except `signature_hex` using the
/// frozen `tbtc-frost-share-repair-authorization/v1` transcript.
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ShareRepairAuthorization {
pub schema: String,
pub session_id: String,
pub wallet_id: String,
pub key_group: String,
pub public_key_package_commitment: String,
pub target_identifier: u16,
pub helper_identifiers: Vec<u16>,
pub threshold: u16,
pub participant_count: u16,
pub old_store_fingerprint: String,
pub new_store_fingerprint: String,
pub recovery_epoch: u64,
pub issued_at_unix: u64,
pub not_before_unix: u64,
pub expires_at_unix: u64,
pub nonce: String,
pub signature_hex: String,
}

#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct BeginShareRepairSessionRequest {
pub authorization: ShareRepairAuthorization,
pub participant_identifier: u16,
}

#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct BeginShareRepairSessionResult {
pub context_digest: String,
pub participant_identifier: u16,
/// Fingerprint of the exact durable store admitted for transport-key
/// derivation. The offline authority signs this value into the transport
/// roster together with the native public key.
pub store_fingerprint: String,
/// Authorization-scoped native transport key. It is deterministic through
/// authorization expiry; only per-envelope ECDH keys are ephemeral.
pub transport_public_key_hex: String,
}

#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct FinishShareRepairSessionRequest {
pub authorization: ShareRepairAuthorization,
pub participant_identifier: u16,
}

#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct FinishShareRepairSessionResult {
pub context_digest: String,
pub participant_identifier: u16,
pub finished: bool,
}

#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ShareRepairEndpointPublicKey {
pub participant_identifier: u16,
pub store_fingerprint: String,
pub public_key_hex: String,
}

#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ShareRepairTransportRoster {
pub schema: String,
pub authorization_digest: String,
pub participant_public_keys: Vec<ShareRepairEndpointPublicKey>,
pub signature_hex: String,
}

#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ShareRepairPart1Request {
pub authorization: ShareRepairAuthorization,
pub helper_identifier: u16,
pub transport_roster: ShareRepairTransportRoster,
}

/// One opaque, native-encrypted repair delta. The scalar plaintext and native
/// transport private key never cross the FFI boundary.
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ShareRepairDelta {
pub context_digest: String,
pub sender_identifier: u16,
pub recipient_identifier: u16,
pub payload_hex: String,
}

#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ShareRepairPart1Result {
pub context_digest: String,
pub helper_identifier: u16,
/// Public package independently read and commitment-checked from this
/// helper's retained session. The target obtains it from the exact helper
/// set; no pre-existing target-store material is required.
pub public_key_package: NativeFrostPublicKeyPackage,
pub deltas: Vec<ShareRepairDelta>,
}

#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ShareRepairPart2Request {
pub authorization: ShareRepairAuthorization,
pub helper_identifier: u16,
pub deltas: Vec<ShareRepairDelta>,
pub transport_roster: ShareRepairTransportRoster,
}

#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ShareRepairSigma {
pub context_digest: String,
pub helper_identifier: u16,
pub payload_hex: String,
}

#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ShareRepairPart2Result {
pub context_digest: String,
pub sigma: ShareRepairSigma,
}

#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct InstallRepairedShareRequest {
pub authorization: ShareRepairAuthorization,
pub public_key_package: NativeFrostPublicKeyPackage,
pub sigmas: Vec<ShareRepairSigma>,
pub transport_roster: ShareRepairTransportRoster,
}

#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct InstallRepairedShareResult {
pub schema: String,
pub session_id: String,
pub key_group: String,
pub target_identifier: u16,
pub recovery_epoch: u64,
pub authorization_digest: String,
pub active_store_fingerprint: String,
pub idempotent: bool,
}

#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
pub struct NativeFrostCommitment {
pub identifier: String,
Expand Down Expand Up @@ -726,6 +881,17 @@ pub struct RetainedKeyPackageInventoryEntry {
pub key_packages: Vec<RetainedKeyPackageInventoryPackage>,
}

#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RetainedKeyPackageInventoryRecoveredSeat {
pub wallet_id: String,
pub key_group: String,
pub participant_seat: u16,
pub recovery_epoch: u64,
pub authorization_digest: String,
pub active_store_fingerprint: String,
}

#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RetainedKeyPackageInventoryResult {
Expand All @@ -737,6 +903,10 @@ pub struct RetainedKeyPackageInventoryResult {
pub state_image_digest: String,
pub inventory_commitment: String,
pub entries: Vec<RetainedKeyPackageInventoryEntry>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub recovered_seats: Vec<RetainedKeyPackageInventoryRecoveredSeat>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub recovery_activation_commitment: Option<String>,
}

#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
Expand Down
Loading
Loading