Skip to content

[Feat] Fix xrpl interface#15

Merged
RogerKSI merged 29 commits intomainfrom
fix-xrpl-interface
Mar 11, 2026
Merged

[Feat] Fix xrpl interface#15
RogerKSI merged 29 commits intomainfrom
fix-xrpl-interface

Conversation

@tanut32039
Copy link
Copy Markdown
Contributor

@tanut32039 tanut32039 commented Feb 27, 2026

Add xrpl sign logic

  • refactor tx encode/decode to codec dir
  • add tss signature verifier for xrpl signing
  • add DERSIgner

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds XRPL (XRP Ledger) signing support to the FKMS (Falcon Key Management Service) alongside existing EVM functionality. The changes introduce TSS (Threshold Signature Scheme) signature verification, XRPL-specific transaction encoding, and extend the gRPC API with new RPC methods for XRPL signing. The implementation includes codec modules for decoding TSS messages (with tick and fixed-point price encoding), EVM transactions, and XRPL transaction construction.

Changes:

  • Added TSS signature verification system with challenge-based verification for secure transaction signing
  • Implemented XRPL signing support with DER signature format and compressed public key handling
  • Extended proto interface with SignXrpl RPC method and supporting message types for XRPL transactions

Reviewed changes

Copilot reviewed 27 out of 28 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/verifier/tss/signature.rs New TSS signature verifier using Schnorr-like verification with challenge computation
src/verifier/tss/message.rs TSS message verification comparing prices from transaction and TSS data
src/codec/tss.rs TSS message decoder supporting fixed-point and tick-based price encoding
src/codec/evm.rs EVM transaction decoder extracting TSS data from legacy and EIP-1559 transactions
src/codec/xrpl.rs XRPL transaction encoder creating OracleSet transactions with price data
src/signer.rs Added XrplSigner trait and XRPL address derivation using RIPEMD-160
src/signer/local.rs Extended LocalSigner to support both ECDSA and DER signatures with compressed keys
src/signer/aws.rs Updated AWS signer interface to support is_compressed parameter
src/signer/signature/ecdsa.rs Added DerSignature type for XRPL signing
src/server/service.rs New unified service implementation with sign_evm and sign_xrpl endpoints
src/server/builder.rs Extended builder to support XRPL signers and TSS verifier
src/server.rs Updated server struct to hold both EVM and XRPL signers
src/server/pre_sign.rs Changed PreSignHook interface to accept prices instead of raw message
src/config/signer/local.rs Added ChainType enum to distinguish EVM and XRPL configurations
src/commands/utils.rs Separated signer loading logic for EVM (coin_type 60) and XRPL (coin_type 144)
src/commands/start.rs Added TSS public key loading from environment variable
src/commands/key.rs Updated key display to show both EVM and XRPL addresses
proto/fkms/v1/signer.proto Added SignXrpl RPC method and supporting message types
Cargo.toml Added dependencies for XRPL support including xrpl-rust and xrpl_binary_codec
.github/workflows/ci.yml Updated Rust toolchain version
README.md Updated documentation to reflect XRPL support

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/verifier/tss/signature.rs Outdated
Comment thread src/commands/utils.rs Outdated
Comment thread src/signer.rs Outdated
Comment thread src/codec/xrpl.rs
Comment thread src/commands/start.rs Outdated
Comment thread Cargo.toml Outdated
Comment thread src/signer/aws.rs Outdated
Comment thread src/signer.rs Outdated
Comment thread src/codec/tss.rs
Comment thread src/codec/xrpl.rs
Comment thread src/commands/start.rs
Comment thread src/commands/utils.rs Outdated
Comment thread src/signer/local.rs Outdated
Comment thread src/server/service.rs Outdated
Comment thread src/server/service.rs Outdated
Comment thread src/server/service.rs Outdated
Comment thread README.md
Comment thread src/server.rs Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 26 changed files in this pull request and generated 9 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread src/commands/utils.rs Outdated
Comment thread src/server/service.rs
Comment thread src/codec/xrpl.rs
Comment thread proto/fkms/v1/signer.proto
Comment thread README.md
Comment thread src/commands/start.rs Outdated
Comment thread src/server/service.rs Outdated
Comment thread src/signer.rs Outdated
Comment thread README.md
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 28 out of 29 changed files in this pull request and generated 10 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread src/config/signer/local.rs
Comment thread src/server/service.rs
Comment thread src/signer/local.rs
Comment thread src/commands/key.rs Outdated
Comment thread README.md Outdated
Comment thread src/server/service.rs
Comment thread src/signer/local.rs
Comment thread src/codec/xrpl.rs
Comment thread README.md Outdated
Comment thread src/config.rs
Comment thread proto/fkms/v1/signer.proto
Comment thread src/signer/local.rs Outdated
Comment thread src/signer/aws.rs
Comment thread src/signer.rs Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 28 out of 29 changed files in this pull request and generated 9 comments.

Comments suppressed due to low confidence (1)

src/signer/aws.rs:72

  • AwsSigner::sign_ecdsa_internal uses SigningAlgorithmSpec::EcdsaSha256 without setting message_type to DIGEST, so AWS KMS will hash the provided bytes with SHA-256 before signing. The new Signer::sign_ecdsa interface is being called with a prehash (Keccak256 digest) in server/service.rs, so the AWS signer will produce signatures over SHA256(keccak(tx)) while the local signer signs keccak(tx) directly. Align AWS signing behavior with the prehash contract (e.g., set message_type appropriately and/or adjust what is passed to KMS).
        let sign_output = self
            .client
            .sign()
            .key_id(&self.key_id)
            .message(Blob::new(message))
            .signing_algorithm(aws_sdk_kms::types::SigningAlgorithmSpec::EcdsaSha256)
            .send()
            .await?;

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread src/server/service.rs Outdated
Comment thread src/codec/xrpl.rs
Comment thread src/signer.rs
Comment thread src/signer/aws.rs Outdated
Comment thread src/verifier/tss/signature.rs
Comment thread README.md
Comment thread README.md Outdated
Comment thread README.md Outdated
Comment thread proto/fkms/v1/signer.proto
Comment thread README.md Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 28 out of 29 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread src/verifier/tss/signature.rs Outdated
Comment thread src/signer.rs
Comment thread README.md
Comment thread src/verifier/tss/signature.rs Outdated
Comment thread src/signer/aws.rs
Comment thread README.md
Comment thread src/server/service.rs Outdated
Tanut Lertwarachai added 2 commits March 11, 2026 16:06
@RogerKSI RogerKSI merged commit 3e1d40b into main Mar 11, 2026
5 checks passed
@RogerKSI RogerKSI deleted the fix-xrpl-interface branch March 11, 2026 09:33
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 28 out of 29 changed files in this pull request and generated 6 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread src/signer/aws.rs
Comment on lines 66 to +87
@@ -52,35 +73,48 @@ impl AwsSigner {
.send()
.await?;

let signature_blob = sign_output
.signature
.ok_or(anyhow::Error::msg("no signature found"))?;
let signature_blob = sign_output.signature.ok_or(anyhow!("no signature found"))?;

let signature = ecdsa::Signature::from_der(signature_blob.as_ref())?;
match self.chain_type {
ChainType::Evm => {
let signature = ecdsa::Signature::from_der(signature_blob.as_ref())?;
let digest = Keccak256::new_with_prefix(message);
let recovery_id = self.find_recovery_id(digest, &signature)?;
let recoverable_signature: EcdsaSignature = (signature, recovery_id);
Ok(recoverable_signature.into_vec())
}
_ => Err(anyhow!("Unsupported Chain Type")),
}
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AwsSigner::sign sends message directly to KMS with SigningAlgorithmSpec::EcdsaSha256 but does not set message_type = DIGEST. In the server, message is already a 32-byte Keccak256 digest, so KMS will hash it with SHA-256 again, producing an invalid signature for EVM usage. Also, recovery-id search re-hashes message via Keccak256::new_with_prefix(message); if message is already a digest, recovery should use a prehash recovery API (or use the exact digest bytes that were signed).

Copilot uses AI. Check for mistakes.
Comment thread src/signer.rs
Comment on lines +27 to +40
pub fn public_key_to_evm_address(public_key: &[u8]) -> anyhow::Result<String> {
// Check exact length
if public_key.len() != 65 {
return Err(anyhow!(
"Invalid public key length for EVM address. Expected 65 bytes, got {}",
public_key.len()
));
}
}

fn public_key_to_evm_address(public_key: &[u8]) -> String {
let mut hasher = sha3::Keccak256::new();
hasher.update(&public_key[1..]); // Skip the first byte (0x04)
let hash = hasher.finalize().to_vec();
format!("0x{}", hex::encode(&hash[12..]))

// We can now safely skip the first byte because we validated length and prefix
hasher.update(&public_key[1..]);
let hash = hasher.finalize();
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public_key_to_evm_address only validates the key length, but the comment says the prefix is validated and the implementation always skips public_key[0]. If a non-uncompressed SEC1 key (or wrong prefix) is passed, this will silently compute the wrong address. Consider validating public_key[0] == 0x04 (and returning an error otherwise) or explicitly accepting both compressed/uncompressed forms and normalizing first.

Copilot uses AI. Check for mistakes.
Comment thread src/signer/local.rs
async fn test_sign_der() {
let pk = hex::decode("45ea1ad910df93d1a021a42f384aad55c7c65d565ad6b2203a4ba50418922a7b")
.unwrap();
let signer = LocalSigner::new(&pk, &ChainType::Evm).unwrap();
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_sign_der constructs the signer with ChainType::Evm, but DER signing is only used for XRPL in LocalSigner::sign. This makes the test inconsistent with the production behavior and could hide regressions in XRPL-specific setup (compressed pubkey/address). Update the test to use ChainType::Xrpl for the signer instance.

Suggested change
let signer = LocalSigner::new(&pk, &ChainType::Evm).unwrap();
let signer = LocalSigner::new(&pk, &ChainType::Xrpl).unwrap();

Copilot uses AI. Check for mistakes.
Comment thread README.md
Comment on lines 174 to 180
@@ -139,6 +179,50 @@
}
```
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

README's GetSignerAddressesResponse example still shows repeated string addresses, but the proto was changed to repeated Signers signers. This is misleading for API consumers; update the example to match proto/fkms/v1/signer.proto.

Copilot uses AI. Check for mistakes.
Comment thread src/codec/xrpl.rs
Comment on lines +103 to +108
// Convert characters to Hex uppercase
let mut hex_str = s.chars().fold(String::new(), |mut acc, c| {
acc.push_str(&format!("{:02X}", c as u32));
acc
});

Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

str_to_hex claims to only support ASCII, but it operates on chars without enforcing is_ascii(). Non-ASCII input will produce variable-length hex (e.g., >2 bytes per char), potentially violating XRPL field-size expectations and breaking encoding. Consider validating s.is_ascii() and encoding via s.as_bytes() instead of iterating chars.

Suggested change
// Convert characters to Hex uppercase
let mut hex_str = s.chars().fold(String::new(), |mut acc, c| {
acc.push_str(&format!("{:02X}", c as u32));
acc
});
// Enforce ASCII-only input to avoid variable-length and invalid encodings
if !s.is_ascii() {
return Err(anyhow!(
"str_to_hex only supports ASCII input, got non-ASCII string: {}",
s
));
}
// Convert bytes to uppercase hex (two hex chars per byte)
let mut hex_str = String::new();
for b in s.as_bytes() {
hex_str.push_str(&format!("{:02X}", b));
}

Copilot uses AI. Check for mistakes.
Comment thread README.md
Comment on lines 58 to 85
[[signer_config.local_signer_configs]]
type = "private_key"
env_variable = "PRIVATE_KEY_1"
encoding = "hex"
chain_type = "evm"

[[signer_config.local_signer_configs]]
type = "mnemonic"
env_variable = "MNEMONIC_1"
coin_type = 60
account = 0
index = 0

[tss]
enable_verify = true

[[tss.groups]]
public_key = "0232a786de4c99679f10fb9a1c94d17737739e413bef385a2f8a26f901a40fdc8b"
expired_time = 1234567890
```

### Supported Local Signer Types

| Type | Description | Required Fields |
| --------------| ---------------------------------------------- | -----------------------------------------------|
| `private_key` | Load private key from an environment variable | `env_variable`, `encoding` |
| `private_key` | Load private key from an environment variable (Currently support only EVM) | `env_variable`, `encoding` |
| `mnemonic` | Load mnemonic from an environment variable | `env_variable`, `coin_type`, `account`, `index`|

Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sample config.toml is missing chain_type for the mnemonic signer config, but the code requires it. Also, the table says private_key currently supports only EVM, but LocalSigner::new supports both EVM and XRPL based on chain_type. Please update the example/table to match the actual configuration requirements and supported chains.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants