lexe-common
Advanced tools
| { | ||
| "git": { | ||
| "sha1": "4b8b1904cc4c047b46048a2028a9b1a90e64f972" | ||
| "sha1": "64d6f049576247f7e48a0ef09d191999b14272b9" | ||
| }, | ||
| "path_in_vcs": "public/lexe-common" | ||
| } |
+25
-25
@@ -16,5 +16,5 @@ # This file is automatically @generated by Cargo. | ||
| name = "anyhow" | ||
| version = "1.0.102" | ||
| version = "1.0.103" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" | ||
| checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" | ||
@@ -506,5 +506,5 @@ [[package]] | ||
| name = "lexe-byte-array" | ||
| version = "0.1.15" | ||
| version = "0.1.16" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "cd32388f466c3447698338b958adf0f9efefd0a0be5c1269f61f9e3ab21cc42b" | ||
| checksum = "dc728990c344e970e15ef71db3c7ae165b1cb6168d3e772849622e4b9a70f648" | ||
| dependencies = [ | ||
@@ -516,3 +516,3 @@ "lexe-hex", | ||
| name = "lexe-common" | ||
| version = "0.1.15" | ||
| version = "0.1.16" | ||
| dependencies = [ | ||
@@ -565,5 +565,5 @@ "anyhow", | ||
| name = "lexe-crypto" | ||
| version = "0.1.15" | ||
| version = "0.1.16" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "1c4ac0ac4e73270b57e4473165a3971e640d9956b7dbd21753ce9a4cfa7b5203" | ||
| checksum = "9318ef44606022ff7ca7798a747ad126805ad9054a073e3378aaa5a1d5bcd5ec" | ||
| dependencies = [ | ||
@@ -586,5 +586,5 @@ "bcs", | ||
| name = "lexe-enclave" | ||
| version = "0.1.15" | ||
| version = "0.1.16" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "b9e2bc1f20c29dd520fa7303e0b4d42fd15911f9a5d416910f8ae1366498ebcd" | ||
| checksum = "ff757e0c4d9f0ba7f5c635f2ce53c25cf2aee8cd3dab63acb5cc2eecb0bdb46e" | ||
| dependencies = [ | ||
@@ -606,11 +606,11 @@ "cfg-if", | ||
| name = "lexe-hex" | ||
| version = "0.1.15" | ||
| version = "0.1.16" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "3ae0a0220e551b7f089e504cc51194814779e8f1746c67db5b1e8b0d38434ee3" | ||
| checksum = "1c08f90b258615b4ada83e4ed35d596cbe6af69cb35512e78460ac8a11b5d753" | ||
| [[package]] | ||
| name = "lexe-serde" | ||
| version = "0.1.15" | ||
| version = "0.1.16" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "7c6dc6bd468deb4a32ce1b4eab412ca088b078ecfd3b2ae1b0d9c6ce4986ff3a" | ||
| checksum = "c5841cf7e9d34aa855ba6a077886980d3a9971a885afd527dfef6a525de009d8" | ||
| dependencies = [ | ||
@@ -624,5 +624,5 @@ "base64", | ||
| name = "lexe-sha256" | ||
| version = "0.1.15" | ||
| version = "0.1.16" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "5a731b44b98afedbf98cd52ecfde6ce783f984a2133ad38185a8c7ee5912cc64" | ||
| checksum = "5fb14bba432279681424729b4ed9ab153dad35f760bc744a634e7e31287db461" | ||
| dependencies = [ | ||
@@ -637,5 +637,5 @@ "lexe-byte-array", | ||
| name = "lexe-std" | ||
| version = "0.1.15" | ||
| version = "0.1.16" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "6f3c4d8096e4c718ea823845c65b7cd1a878b929d7c94caefd9e988b167cdbbb" | ||
| checksum = "77db2d58961be8209cacea384e9c62c56bb447e6c52e36fb79ba017dd3b82763" | ||
| dependencies = [ | ||
@@ -659,5 +659,5 @@ "ref-cast", | ||
| name = "lightning" | ||
| version = "0.2.2" | ||
| version = "0.2.4" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "4c90397b635e3ece6b9a723fb470a46cb9b3592f217d72e40540a5fada00289d" | ||
| checksum = "63a1ab99a13ab3343a9b4d1d25c455bf72322819fc5a5f05c5182e704528a583" | ||
| dependencies = [ | ||
@@ -677,5 +677,5 @@ "bech32", | ||
| name = "lightning-invoice" | ||
| version = "0.34.0" | ||
| version = "0.34.1" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "b85e5e14bcdb30d746e9785b04f27938292e8944f78f26517e01e91691f6b3f2" | ||
| checksum = "47d83bd798e04ab9eecc8bbef1fa17d3808859bcdc0406bd16c55d51c8834444" | ||
| dependencies = [ | ||
@@ -700,5 +700,5 @@ "bech32", | ||
| name = "lightning-types" | ||
| version = "0.3.1" | ||
| version = "0.3.2" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "cb1aac93f22f2c2eac8a0ee83bb1a1ea58673caa2c82847302710b83364d04e6" | ||
| checksum = "c211dfcff95ca308247da8b1e0e81604bc9e568239967cd2c34572558511e869" | ||
| dependencies = [ | ||
@@ -807,5 +807,5 @@ "bitcoin", | ||
| name = "possiblyrandom" | ||
| version = "0.2.0" | ||
| version = "0.2.1" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "1b122a615d72104fb3d8b26523fdf9232cd8ee06949fb37e4ce3ff964d15dffd" | ||
| checksum = "9c564dbf654befd49035528299f1208a40508f6e07efb11c163444e304e4484f" | ||
| dependencies = [ | ||
@@ -812,0 +812,0 @@ "getrandom 0.2.17", |
+10
-10
@@ -16,3 +16,3 @@ # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO | ||
| name = "lexe-common" | ||
| version = "0.1.15" | ||
| version = "0.1.16" | ||
| authors = [ | ||
@@ -105,21 +105,21 @@ "Max Fang <max@lexe.app>", | ||
| [dependencies.lexe-byte-array] | ||
| version = "0.1.15" | ||
| version = "0.1.16" | ||
| [dependencies.lexe-crypto] | ||
| version = "0.1.15" | ||
| version = "0.1.16" | ||
| [dependencies.lexe-enclave] | ||
| version = "0.1.15" | ||
| version = "0.1.16" | ||
| [dependencies.lexe-hex] | ||
| version = "0.1.15" | ||
| version = "0.1.16" | ||
| [dependencies.lexe-serde] | ||
| version = "0.1.15" | ||
| version = "0.1.16" | ||
| [dependencies.lexe-sha256] | ||
| version = "0.1.15" | ||
| version = "0.1.16" | ||
| [dependencies.lexe-std] | ||
| version = "0.1.15" | ||
| version = "0.1.16" | ||
| features = ["ref-cast"] | ||
@@ -213,7 +213,7 @@ | ||
| [dev-dependencies.lexe-crypto] | ||
| version = "0.1.15" | ||
| version = "0.1.16" | ||
| features = ["test-utils"] | ||
| [dev-dependencies.lexe-enclave] | ||
| version = "0.1.15" | ||
| version = "0.1.16" | ||
| features = ["test-utils"] | ||
@@ -220,0 +220,0 @@ |
+69
-40
@@ -23,3 +23,3 @@ // bearer auth v1 | ||
| pub enum Error { | ||
| #[error("error verifying signed bearer auth request: {0}")] | ||
| #[error("Error verifying signed bearer auth request: {0}")] | ||
| UserVerifyError(#[source] ed25519::Error), | ||
@@ -30,21 +30,24 @@ | ||
| #[error("issued timestamp is too far from current auth server clock")] | ||
| #[error("Issued timestamp is too far from current auth server clock")] | ||
| ClockDrift, | ||
| #[error("auth token or auth request is expired")] | ||
| #[error("Auth token or auth request is expired")] | ||
| Expired, | ||
| #[error("timestamp is not a valid unix timestamp")] | ||
| #[error("Auth token is not valid yet")] | ||
| NotYetValid, | ||
| #[error("Timestamp is not a valid unix timestamp")] | ||
| InvalidTimestamp, | ||
| #[error("requested token lifetime is too long")] | ||
| #[error("Requested token lifetime is too long")] | ||
| InvalidLifetime, | ||
| #[error("user not signed up yet")] | ||
| #[error("User not signed up yet")] | ||
| NoUser, | ||
| #[error("bearer auth token is not valid base64")] | ||
| #[error("Bearer auth token is not valid base64")] | ||
| Base64Decode, | ||
| #[error("bearer auth token was not provided")] | ||
| #[error("Bearer auth token was not provided")] | ||
| Missing, | ||
@@ -55,6 +58,9 @@ | ||
| #[error( | ||
| "auth token's granted scope ({granted:?}) is not sufficient for \ | ||
| "Auth token's granted scope ({granted:?}) is not sufficient for \ | ||
| requested scope ({requested:?})" | ||
| )] | ||
| InsufficientScope { granted: Scope, requested: Scope }, | ||
| InsufficientScope { | ||
| granted: LexeScope, | ||
| requested: LexeScope, | ||
| }, | ||
| } | ||
@@ -133,7 +139,6 @@ | ||
| /// The allowed API scope for the bearer auth token. If unset, the issued | ||
| /// token currently defaults to [`Scope::All`]. | ||
| /// The [`LexeScope`] requested for the new bearer auth token. | ||
| // TODO(phlip9): implement proper scope attenuation from identity's allowed | ||
| // scopes | ||
| pub scope: Option<Scope>, | ||
| pub scope: LexeScope, | ||
| } | ||
@@ -167,15 +172,38 @@ | ||
| v1: BearerAuthRequestWireV1, | ||
| scope: Option<Scope>, | ||
| // node-v0.7.9 added this field as `Option` along with the V2 wire format. | ||
| // | ||
| // We must keep this `Option` so that we can continue deserializing those | ||
| // older `None` requests (and assume `LexeScope::All` in such a case); | ||
| // current clients always send `Some`. | ||
| scope: Option<LexeScope>, | ||
| } | ||
| /// The allowed API scope for the bearer auth token. | ||
| /// An infra-level scope which gates a bearer auth token's access to Lexe-run | ||
| /// services and resources. | ||
| /// | ||
| /// This is distinct from the *application-level* authorization that determines | ||
| /// which node endpoints a given client credential may call; that lives with the | ||
| /// revocable client machinery, not here. A `LexeScope` answers "what may this | ||
| /// token do against *Lexe's* infrastructure", e.g. the backend or the gateway | ||
| /// CONNECT proxy. | ||
| /// | ||
| /// Scopes form a lattice: a token granted a broader scope is accepted wherever | ||
| /// a narrower scope is required (see [`LexeScope::has_permission_for`]). | ||
| #[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))] | ||
| #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] | ||
| pub enum Scope { | ||
| /// The token is valid for all scopes. | ||
| #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] | ||
| pub enum LexeScope { | ||
| /// Full access to all Lexe-run services. Short-lived (max 1 hour). | ||
| All, | ||
| /// The token is only allowed to connect to a user node via the gateway. | ||
| /// Authorizes the gateway to act as the user's proxy, in either of two | ||
| /// ways: | ||
| /// - a CONNECT tunnel to the user's own node, or | ||
| /// - a request the gateway makes to the backend (`AppBackendApi`) on the | ||
| /// user's behalf, using its own backend credentials. | ||
| /// | ||
| /// This grants neither direct backend access nor forwarding of this token | ||
| /// to the backend; that's [`LexeScope::All`]. May be long-lived (max 20 | ||
| /// years), so it can back a long-lived client credential. | ||
| // TODO(phlip9): should be a fine-grained scope | ||
| NodeConnect, | ||
| GatewayProxy, | ||
| // | ||
@@ -245,3 +273,3 @@ // // TODO(phlip9): fine-grained scopes? | ||
| // ownership of a user_pk. | ||
| ed25519::verify_signed_struct(ed25519::accept_any_signer, serialized) | ||
| ed25519::verify::signed_struct_by_any_signer(serialized) | ||
| .map_err(Error::UserVerifyError) | ||
@@ -271,3 +299,3 @@ } | ||
| token_lifetime_secs: u32, | ||
| scope: Option<Scope>, | ||
| scope: LexeScope, | ||
| ) -> Self { | ||
@@ -299,11 +327,14 @@ Self { | ||
| match wire { | ||
| // V1 predates scopes; fall back to the broadest scope for these | ||
| // (very old) clients. | ||
| BearerAuthRequestWire::V1(v1) => Self { | ||
| request_timestamp_secs: v1.request_timestamp_secs, | ||
| lifetime_secs: v1.lifetime_secs, | ||
| scope: None, | ||
| scope: LexeScope::All, | ||
| }, | ||
| // A V2 client that omits the scope likewise defaults to `All`. | ||
| BearerAuthRequestWire::V2(v2) => Self { | ||
| request_timestamp_secs: v2.v1.request_timestamp_secs, | ||
| lifetime_secs: v2.v1.lifetime_secs, | ||
| scope: v2.scope, | ||
| scope: v2.scope.unwrap_or(LexeScope::All), | ||
| }, | ||
@@ -321,3 +352,3 @@ } | ||
| }, | ||
| scope: req.scope, | ||
| scope: Some(req.scope), | ||
| }) | ||
@@ -335,3 +366,3 @@ } | ||
| // of a user_pk. | ||
| ed25519::verify_signed_struct(ed25519::accept_any_signer, serialized) | ||
| ed25519::verify::signed_struct_by_any_signer(serialized) | ||
| .map_err(Error::UserVerifyError) | ||
@@ -401,13 +432,11 @@ } | ||
| // --- impl Scope --- // | ||
| // --- impl LexeScope --- // | ||
| impl Scope { | ||
| /// Returns `true` if the `requested_scope` is allowed by this granted | ||
| /// scope. | ||
| pub fn has_permission_for(&self, requested_scope: &Self) -> bool { | ||
| let granted_scope = self; | ||
| match (granted_scope, requested_scope) { | ||
| (Scope::All, _) => true, | ||
| (Scope::NodeConnect, Scope::All) => false, | ||
| (Scope::NodeConnect, Scope::NodeConnect) => true, | ||
| impl LexeScope { | ||
| /// Returns `true` if this granted scope covers the `requested` scope. | ||
| pub fn has_permission_for(&self, requested: &Self) -> bool { | ||
| match (self, requested) { | ||
| (LexeScope::All, _) => true, | ||
| (LexeScope::GatewayProxy, LexeScope::All) => false, | ||
| (LexeScope::GatewayProxy, LexeScope::GatewayProxy) => true, | ||
| } | ||
@@ -472,3 +501,3 @@ } | ||
| }, | ||
| scope: Some(Scope::NodeConnect), | ||
| scope: Some(LexeScope::GatewayProxy), | ||
| }); | ||
@@ -480,3 +509,3 @@ bcs_roundtrip_ok(&hex::decode(input).unwrap(), &req); | ||
| fn test_auth_scope_canonical() { | ||
| bcs_roundtrip_proptest::<Scope>(); | ||
| bcs_roundtrip_proptest::<LexeScope>(); | ||
| } | ||
@@ -487,7 +516,7 @@ | ||
| let input = b"\x00"; | ||
| let scope = Scope::All; | ||
| let scope = LexeScope::All; | ||
| bcs_roundtrip_ok(input, &scope); | ||
| let input = b"\x01"; | ||
| let scope = Scope::NodeConnect; | ||
| let scope = LexeScope::GatewayProxy; | ||
| bcs_roundtrip_ok(input, &scope); | ||
@@ -494,0 +523,0 @@ } |
@@ -16,3 +16,6 @@ //! Information about a client | ||
| use crate::{ | ||
| api::{auth::Scope, user::UserPk}, | ||
| api::{ | ||
| auth::{BearerAuthToken, LexeScope}, | ||
| user::UserPk, | ||
| }, | ||
| time::TimestampMs, | ||
@@ -76,3 +79,3 @@ }; | ||
| // TODO(max): This scope is currently ineffective. | ||
| pub scope: Scope, | ||
| pub scope: LexeScope, | ||
| /// Whether this client has been revoked. Revocation is permanent. | ||
@@ -123,3 +126,3 @@ pub is_revoked: bool, | ||
| /// The authorization scopes allowed for this client. | ||
| pub scope: Scope, | ||
| pub scope: LexeScope, | ||
| } | ||
@@ -137,2 +140,3 @@ | ||
| pub pubkey: ed25519::PublicKey, | ||
| /// When this client was created. | ||
@@ -156,2 +160,7 @@ pub created_at: TimestampMs, | ||
| pub rev_client_cert_key_der: Vec<u8>, | ||
| /// A long-lived [`LexeScope::GatewayProxy`] token for connecting to the | ||
| /// user's node via the gateway proxy. Always `Some` for user nodes. | ||
| #[serde(skip_serializing_if = "Option::is_none")] | ||
| pub gateway_proxy_token: Option<BearerAuthToken>, | ||
| } | ||
@@ -184,3 +193,3 @@ | ||
| #[serde(skip_serializing_if = "none")] | ||
| pub scope: Option<Scope>, | ||
| pub scope: Option<LexeScope>, | ||
@@ -288,3 +297,3 @@ /// Set this to revoke or unrevoke the client. Revocation is permanent, so | ||
| label: Some("deez".to_string()), | ||
| scope: Scope::All, | ||
| scope: LexeScope::All, | ||
| is_revoked: false, | ||
@@ -291,0 +300,0 @@ }; |
+1
-1
@@ -249,3 +249,3 @@ use std::{collections::HashSet, fmt, str::FromStr}; | ||
| fn from(pk: ed25519::PublicKey) -> Self { | ||
| Self::new(pk.into_inner()) | ||
| Self::new(pk.to_array()) | ||
| } | ||
@@ -252,0 +252,0 @@ } |
+21
-4
@@ -81,3 +81,5 @@ use std::{include_bytes, time::Duration}; | ||
| /// The maximum amount of liquidity that Lexe will supply to a user in one tx. | ||
| pub const MAX_LIQUIDITY_SAT: u32 = 10_000_000; // 0.1 BTC | ||
| // | ||
| // TODO(phlip9): increase once new liquidity model is in | ||
| pub const MAX_LIQUIDITY_SAT: u32 = 1_000_000; // 0.01 BTC | ||
@@ -100,6 +102,21 @@ /// User nodes and the LSP will reject new inbound channels with total channel | ||
| /// Unconditional extra fee the LSP<->user channel funder will pay for | ||
| /// coop-close in order to avoid a potential force-close due to fee | ||
| /// disagreement. | ||
| /// | ||
| /// During modern "legacy" (not "simple_close") coop-close, the channel funder | ||
| /// proposes a `[min fee, max fee]` range that it's willing to pay. An LDK | ||
| /// counterparty then chooses the `max_fee`. | ||
| /// | ||
| /// An LDK funder determines the `max_fee` by estimating the feerate for | ||
| /// [`ConfirmationTarget::NonAnchorChannelFee`] plus this | ||
| /// `FORCE_CLOSE_AVOIDANCE_MAX_FEE_SATS` value. | ||
| /// | ||
| /// 2026-06-24: Prior to this date, this was 1000 sats, which meant all our | ||
| /// LSP<->User coop-closes overpaid on-chain fees by ~7-8x market rate. | ||
| /// | ||
| /// See: [`lightning::util::config::ChannelConfig::force_close_avoidance_max_fee_satoshis`] | ||
| // | ||
| // 1,000 sats = $1.00 assuming $100k/BTC | ||
| pub const FORCE_CLOSE_AVOIDANCE_MAX_FEE_SATS: u64 = 1_000; | ||
| /// | ||
| /// [`ConfirmationTarget::NonAnchorChannelFee`]: lightning::chain::chaininterface::ConfirmationTarget::NonAnchorChannelFee | ||
| pub const FORCE_CLOSE_AVOIDANCE_MAX_FEE_SATS: u64 = 50; | ||
@@ -106,0 +123,0 @@ // --- Persistence --- // |
+2
-1
@@ -8,2 +8,3 @@ use std::{fmt, num::NonZeroU32, str::FromStr}; | ||
| }; | ||
| use lexe_byte_array::ByteArray; | ||
| use lexe_crypto::{ | ||
@@ -201,3 +202,3 @@ aes::{self, AesMasterKey}, | ||
| pub fn derive_user_pk(&self) -> UserPk { | ||
| UserPk::new(self.derive_user_key_pair().public_key().into_inner()) | ||
| UserPk::new(self.derive_user_key_pair().public_key().to_array()) | ||
| } | ||
@@ -204,0 +205,0 @@ |
@@ -69,2 +69,10 @@ //! This module contains [`Arbitrary`]-like [`Strategy`]s for generating various | ||
| /// Like [`any_string`], but bounds the UTF-8 byte length to `max_bytes`. | ||
| pub fn any_bounded_string(max_bytes: usize) -> impl Strategy<Value = String> { | ||
| any_string().prop_map(move |mut s| { | ||
| lexe_std::string::truncate_bytes(&mut s, max_bytes); | ||
| s | ||
| }) | ||
| } | ||
| /// An [`Option`] version of [`any_string`]. | ||
@@ -71,0 +79,0 @@ /// |
@@ -170,3 +170,4 @@ use std::{ | ||
| let (ser_value, signed_value) = key_pair.sign_struct(&value).unwrap(); | ||
| let signed_value2 = pubkey.verify_self_signed_struct(&ser_value).unwrap(); | ||
| let signed_value2 = | ||
| ed25519::verify::signed_struct_by_signer(pubkey, &ser_value).unwrap(); | ||
| let (ser_value2, _) = key_pair.sign_struct(signed_value2.inner()).unwrap(); | ||
@@ -173,0 +174,0 @@ |
Sorry, the diff of this file is not supported yet