Skip to content

Commit

Permalink
Merge pull request #138 from xch-dev/secp-signer
Browse files Browse the repository at this point in the history
Secp signing utilities
  • Loading branch information
Rigidity authored Dec 2, 2024
2 parents 00512e4 + 2716380 commit a14a7cc
Show file tree
Hide file tree
Showing 17 changed files with 487 additions and 121 deletions.
4 changes: 4 additions & 0 deletions Cargo.lock

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

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ anyhow = { workspace = true }
hex-literal = { workspace = true }
chia-protocol = { workspace = true }
chia-puzzles = { workspace = true }
chia-bls = { workspace = true }
chia-bls = { workspace = true }
clvm-utils = { workspace = true }
clvm-traits = { workspace = true, features = ["derive"] }
clvmr = { workspace = true }
Expand Down Expand Up @@ -134,6 +134,7 @@ napi-derive = "2.12.2"
napi = { version = "2.12.2", default-features = false }
paste = "1.0.15"
bigdecimal = "0.4.6"
k256 = "0.13.4"

[profile.release]
lto = true
Expand Down
4 changes: 4 additions & 0 deletions crates/chia-sdk-signer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,12 @@ clvm-traits = { workspace = true }
clvmr = { workspace = true }
thiserror = { workspace = true }
chia-sdk-types = { workspace = true }
k256 = { workspace = true }

[dev-dependencies]
chia-puzzles = { workspace = true }
hex = { workspace = true }
hex-literal = { workspace = true }
rand = { workspace = true }
rand_chacha = { workspace = true }
anyhow = { workspace = true }
5 changes: 5 additions & 0 deletions crates/chia-sdk-signer/src/bls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mod agg_sig_constants;
mod required_bls_signature;

pub use agg_sig_constants::*;
pub use required_bls_signature::*;
82 changes: 82 additions & 0 deletions crates/chia-sdk-signer/src/bls/required_bls_signature.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use chia_bls::PublicKey;
use chia_consensus::gen::make_aggsig_final_message::u64_to_bytes;
use chia_protocol::{Bytes, Bytes32, Coin};
use chia_sdk_types::{AggSig, AggSigKind};

use super::AggSigConstants;

#[derive(Debug, Clone)]
pub struct RequiredBlsSignature {
pub public_key: PublicKey,
pub raw_message: Bytes,
pub appended_info: Vec<u8>,
pub domain_string: Option<Bytes32>,
}

impl RequiredBlsSignature {
/// Converts a known [`AggSig`] condition to a `RequiredSignature` if possible.
pub fn from_condition(coin: &Coin, condition: AggSig, constants: &AggSigConstants) -> Self {
let domain_string;

let public_key = condition.public_key;
let message = condition.message;

let appended_info = match condition.kind {
AggSigKind::Parent => {
domain_string = constants.parent();
coin.parent_coin_info.to_vec()
}
AggSigKind::Puzzle => {
domain_string = constants.puzzle();
coin.puzzle_hash.to_vec()
}
AggSigKind::Amount => {
domain_string = constants.amount();
u64_to_bytes(coin.amount)
}
AggSigKind::PuzzleAmount => {
domain_string = constants.puzzle_amount();
let puzzle = coin.puzzle_hash;
[puzzle.to_vec(), u64_to_bytes(coin.amount)].concat()
}
AggSigKind::ParentAmount => {
domain_string = constants.parent_amount();
let parent = coin.parent_coin_info;
[parent.to_vec(), u64_to_bytes(coin.amount)].concat()
}
AggSigKind::ParentPuzzle => {
domain_string = constants.parent_puzzle();
[coin.parent_coin_info.to_vec(), coin.puzzle_hash.to_vec()].concat()
}
AggSigKind::Unsafe => {
return Self {
public_key,
raw_message: message,
appended_info: Vec::new(),
domain_string: None,
}
}
AggSigKind::Me => {
domain_string = constants.me();
coin.coin_id().to_vec()
}
};

Self {
public_key,
raw_message: message,
appended_info,
domain_string: Some(domain_string),
}
}

/// Computes the message that needs to be signed.
pub fn message(&self) -> Vec<u8> {
let mut message = Vec::from(self.raw_message.as_ref());
message.extend(&self.appended_info);
if let Some(domain_string) = self.domain_string {
message.extend(domain_string.to_bytes());
}
message
}
}
3 changes: 3 additions & 0 deletions crates/chia-sdk-signer/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,7 @@ pub enum SignerError {

#[error("Infinity public key")]
InfinityPublicKey,

#[error("Invalid secp key")]
InvalidSecpKey(#[from] k256::ecdsa::Error),
}
6 changes: 4 additions & 2 deletions crates/chia-sdk-signer/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
mod agg_sig_constants;
mod bls;
mod error;
mod required_signature;
mod secp;

pub use agg_sig_constants::*;
pub use bls::*;
pub use error::*;
pub use required_signature::*;
pub use secp::*;
146 changes: 31 additions & 115 deletions crates/chia-sdk-signer/src/required_signature.rs
Original file line number Diff line number Diff line change
@@ -1,76 +1,19 @@
use chia_bls::PublicKey;
use chia_protocol::{Bytes, Bytes32, Coin, CoinSpend};
use chia_sdk_types::{run_puzzle, AggSig, AggSigKind, Condition};
use chia_protocol::CoinSpend;
use chia_sdk_types::Condition;
use clvm_traits::{FromClvm, ToClvm};
use clvmr::Allocator;
use clvmr::{run_program, Allocator, ChiaDialect};

use crate::{AggSigConstants, SignerError};
use crate::{
AggSigConstants, RequiredBlsSignature, RequiredSecpSignature, SecpDialect, SignerError,
};

#[derive(Debug, Clone)]
pub struct RequiredSignature {
public_key: PublicKey,
raw_message: Bytes,
appended_info: Vec<u8>,
domain_string: Option<Bytes32>,
pub enum RequiredSignature {
Bls(RequiredBlsSignature),
Secp(RequiredSecpSignature),
}

impl RequiredSignature {
/// Converts a known [`AggSig`] condition to a `RequiredSignature` if possible.
pub fn from_condition(coin: &Coin, condition: AggSig, constants: &AggSigConstants) -> Self {
let domain_string;

let public_key = condition.public_key;
let message = condition.message;

let appended_info = match condition.kind {
AggSigKind::Parent => {
domain_string = constants.parent();
coin.parent_coin_info.to_vec()
}
AggSigKind::Puzzle => {
domain_string = constants.puzzle();
coin.puzzle_hash.to_vec()
}
AggSigKind::Amount => {
domain_string = constants.amount();
u64_to_bytes(coin.amount)
}
AggSigKind::PuzzleAmount => {
domain_string = constants.puzzle_amount();
let puzzle = coin.puzzle_hash;
[puzzle.to_vec(), u64_to_bytes(coin.amount)].concat()
}
AggSigKind::ParentAmount => {
domain_string = constants.parent_amount();
let parent = coin.parent_coin_info;
[parent.to_vec(), u64_to_bytes(coin.amount)].concat()
}
AggSigKind::ParentPuzzle => {
domain_string = constants.parent_puzzle();
[coin.parent_coin_info.to_vec(), coin.puzzle_hash.to_vec()].concat()
}
AggSigKind::Unsafe => {
return Self {
public_key,
raw_message: message,
appended_info: Vec::new(),
domain_string: None,
}
}
AggSigKind::Me => {
domain_string = constants.me();
coin.coin_id().to_vec()
}
};

Self {
public_key,
raw_message: message,
appended_info,
domain_string: Some(domain_string),
}
}

/// Calculates the required signatures for a coin spend.
/// All of these signatures aggregated together should be
/// sufficient, unless secp keys are used as well.
Expand All @@ -81,7 +24,8 @@ impl RequiredSignature {
) -> Result<Vec<Self>, SignerError> {
let puzzle = coin_spend.puzzle_reveal.to_clvm(allocator)?;
let solution = coin_spend.solution.to_clvm(allocator)?;
let output = run_puzzle(allocator, puzzle, solution)?;
let dialect = SecpDialect::new(ChiaDialect::new(0));
let output = run_program(allocator, &dialect, puzzle, solution, 11_000_000_000)?.1;
let conditions = Vec::<Condition>::from_clvm(allocator, output)?;

let mut result = Vec::new();
Expand All @@ -95,7 +39,15 @@ impl RequiredSignature {
return Err(SignerError::InfinityPublicKey);
}

result.push(Self::from_condition(&coin_spend.coin, agg_sig, constants));
result.push(Self::Bls(RequiredBlsSignature::from_condition(
&coin_spend.coin,
agg_sig,
constants,
)));
}

for item in dialect.collect() {
result.push(Self::Secp(item));
}

Ok(result)
Expand All @@ -115,52 +67,16 @@ impl RequiredSignature {
}
Ok(required_signatures)
}

/// The public key required to verify the signature.
pub fn public_key(&self) -> PublicKey {
self.public_key
}

/// The message field of the condition, without anything appended.
pub fn raw_message(&self) -> &[u8] {
self.raw_message.as_ref()
}

/// Additional coin information that is appended to the condition's message.
pub fn appended_info(&self) -> &[u8] {
&self.appended_info
}

/// The domain string that is appended to the condition's message.
pub fn domain_string(&self) -> Option<Bytes32> {
self.domain_string
}

/// Computes the message that needs to be signed.
pub fn final_message(&self) -> Vec<u8> {
let mut message = Vec::from(self.raw_message.as_ref());
message.extend(&self.appended_info);
if let Some(domain_string) = self.domain_string {
message.extend(domain_string.to_bytes());
}
message
}
}

fn u64_to_bytes(value: u64) -> Vec<u8> {
let mut allocator = Allocator::new();
let atom = allocator.new_number(value.into()).unwrap();
allocator.atom(atom).as_ref().to_vec()
}

#[cfg(test)]
mod tests {
use super::*;

use chia_bls::{master_to_wallet_unhardened, SecretKey};
use chia_protocol::Bytes32;
use chia_protocol::{Bytes, Bytes32, Coin};
use chia_puzzles::DeriveSynthetic;
use chia_sdk_types::MAINNET_CONSTANTS;
use chia_sdk_types::{AggSig, AggSigKind, MAINNET_CONSTANTS};
use hex_literal::hex;

#[test]
Expand Down Expand Up @@ -241,21 +157,21 @@ mod tests {
let constants = AggSigConstants::from(&*MAINNET_CONSTANTS);

for (condition, appended_info, domain_string) in cases {
let required = RequiredSignature::from_condition(&coin, condition, &constants);
let required = RequiredBlsSignature::from_condition(&coin, condition, &constants);

assert_eq!(required.public_key(), public_key);
assert_eq!(required.raw_message(), message.as_ref());
assert_eq!(hex::encode(required.appended_info()), appended_info);
assert_eq!(required.domain_string().map(hex::encode), domain_string);
assert_eq!(required.public_key, public_key);
assert_eq!(required.raw_message, message);
assert_eq!(hex::encode(&required.appended_info), appended_info);
assert_eq!(required.domain_string.map(hex::encode), domain_string);

let mut message = Vec::<u8>::new();
message.extend(required.raw_message());
message.extend(required.appended_info());
if let Some(domain_string) = required.domain_string() {
message.extend(required.raw_message.as_ref());
message.extend(&required.appended_info);
if let Some(domain_string) = required.domain_string {
message.extend(domain_string.to_bytes());
}

assert_eq!(hex::encode(message), hex::encode(required.final_message()));
assert_eq!(hex::encode(message), hex::encode(required.message()));
}
}
}
Loading

0 comments on commit a14a7cc

Please sign in to comment.