Skip to content

Commit 9ea6c01

Browse files
committed
feat: support solana customs
1 parent 3e00cb7 commit 9ea6c01

File tree

14 files changed

+673
-117
lines changed

14 files changed

+673
-117
lines changed

Cargo.lock

Lines changed: 169 additions & 78 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ ic-agent = "0.39"
3434
ic-cdk = "0.17"
3535
ic-canister-log = "0.2"
3636
ic-canisters-http-types = { git = "https://github.com/dfinity/ic", package = "ic-canisters-http-types" }
37-
ic-crypto-ed25519 = { git = "https://github.com/dfinity/ic", package = "ic-crypto-ed25519" }
38-
ic-management-canister-types = { git = "https://github.com/dfinity/ic", package = "ic-management-canister-types" }
37+
ic-crypto-ed25519 = { package = "ic-ed25519", version = "0.2.0"}
38+
ic-management-canister-types = "0.1.0"
3939
ic-test-utilities-load-wasm = { git = "https://github.com/dfinity/ic", rev = "release-2024-09-26_01-31-base" }
4040
ic-metrics-encoder = "1"
4141
ic-stable-structures = "0.6"

dfx.json

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
{
22
"canisters": {
3-
"solana_rpc": {
4-
"type": "custom",
3+
"ic_solana_rpc": {
4+
"gzip": true,
55
"candid": "src/ic-solana-rpc/ic-solana-rpc.did",
6-
"wasm": "ic-solana-rpc.wasm.gz",
7-
"init_arg": "(record {})",
8-
"gzip": true
6+
"package": "ic-solana-rpc",
7+
"type": "custom",
8+
"wasm": "target/wasm32-unknown-unknown/release/ic-solana-rpc.wasm",
9+
"metadata": [
10+
{
11+
"name": "candid:service"
12+
}
13+
]
914
},
1015
"solana_rpc_demo": {
1116
"type": "custom",

src/ic-solana-rpc/src/http.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,12 +118,18 @@ pub fn serve_logs(request: HttpRequest) -> HttpResponse {
118118

119119
match request.raw_query_param("priority").map(Priority::from_str) {
120120
Some(Ok(priority)) => match priority {
121-
Priority::Info => log.push_logs(Priority::Info),
122-
Priority::Debug => log.push_logs(Priority::Debug),
121+
Priority::INFO => log.push_logs(Priority::INFO),
122+
Priority::DEBUG => log.push_logs(Priority::DEBUG),
123+
Priority::WARNING => log.push_logs(Priority::WARNING),
124+
Priority::ERROR => log.push_logs(Priority::ERROR),
125+
Priority::CRITICAL => log.push_logs(Priority::CRITICAL),
123126
},
124127
_ => {
125-
log.push_logs(Priority::Info);
126-
log.push_logs(Priority::Debug);
128+
log.push_logs(Priority::INFO);
129+
log.push_logs(Priority::DEBUG);
130+
log.push_logs(Priority::WARNING);
131+
log.push_logs(Priority::ERROR);
132+
log.push_logs(Priority::CRITICAL);
127133
}
128134
}
129135

src/ic-solana-wallet/src/eddsa.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ use std::{
55

66
use candid::{CandidType, Principal};
77
use ic_management_canister_types::{
8-
DerivationPath, SchnorrAlgorithm, SchnorrKeyId, SchnorrPublicKeyArgs, SchnorrPublicKeyResponse,
9-
SignWithSchnorrArgs, SignWithSchnorrReply,
8+
SchnorrAlgorithm, SchnorrKeyId, SchnorrPublicKeyArgs, SchnorrPublicKeyResult, SignWithSchnorrArgs,
9+
SignWithSchnorrResult,
1010
};
1111
use serde::{Deserialize, Serialize};
1212
use serde_bytes::ByteBuf;
@@ -49,12 +49,12 @@ impl FromStr for SchnorrKey {
4949

5050
/// Fetches the ed25519 public key from the schnorr canister.
5151
pub async fn eddsa_public_key(key: SchnorrKey, derivation_path: Vec<ByteBuf>) -> Vec<u8> {
52-
let res: Result<(SchnorrPublicKeyResponse,), _> = ic_cdk::call(
52+
let res: Result<(SchnorrPublicKeyResult,), _> = ic_cdk::call(
5353
Principal::management_canister(),
5454
"schnorr_public_key",
5555
(SchnorrPublicKeyArgs {
5656
canister_id: None,
57-
derivation_path: DerivationPath::new(derivation_path),
57+
derivation_path: derivation_path.iter().map(|p| p.to_vec()).collect(),
5858
key_id: SchnorrKeyId {
5959
algorithm: SchnorrAlgorithm::Ed25519,
6060
name: key.to_string(),
@@ -70,12 +70,12 @@ pub async fn eddsa_public_key(key: SchnorrKey, derivation_path: Vec<ByteBuf>) ->
7070
pub async fn sign_with_eddsa(key: SchnorrKey, derivation_path: Vec<ByteBuf>, message: Vec<u8>) -> Vec<u8> {
7171
ic_cdk::api::call::msg_cycles_accept128(EDDSA_SIGN_COST);
7272

73-
let res: Result<(SignWithSchnorrReply,), _> = ic_cdk::api::call::call_with_payment(
73+
let res: Result<(SignWithSchnorrResult,), _> = ic_cdk::api::call::call_with_payment(
7474
Principal::management_canister(),
7575
"sign_with_schnorr",
7676
(SignWithSchnorrArgs {
7777
message,
78-
derivation_path: DerivationPath::new(derivation_path),
78+
derivation_path: derivation_path.iter().map(|p| p.to_vec()).collect(),
7979
key_id: SchnorrKeyId {
8080
algorithm: SchnorrAlgorithm::Ed25519,
8181
name: key.to_string(),

src/ic-solana/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,33 @@ license = { workspace = true }
99
edition = { workspace = true }
1010

1111
[dependencies]
12+
bip32 = { version = "0.5.1", features = ["k256"] }
1213
base64 = { workspace = true }
1314
bincode = "1.3.3"
1415
bs58 = "0.5.1"
16+
curve25519-dalek = "4.1.3"
1517
candid = { workspace = true }
18+
ed25519-dalek = "2.1"
1619
flate2 = "1.0"
1720
futures = { workspace = true }
1821
getrandom = { version = "0.2", features = ["custom"] }
22+
hex = "0.4"
1923
ic-canister-log = { workspace = true }
24+
ic-canisters-http-types = { workspace = true }
25+
ic-management-canister-types = { workspace = true }
2026
ic-cdk = { workspace = true }
2127
ic-crypto-ed25519 = { workspace = true }
2228
ic-metrics-encoder = { workspace = true }
2329
ic-sha3 = "1"
2430
serde = { workspace = true }
31+
serde_derive = "1.0.208"
2532
serde_json = { workspace = true }
2633
serde_bytes = { workspace = true }
2734
serde-big-array = "0.5.1"
35+
sha2 = "0.10.8"
2836
thiserror = { workspace = true }
2937
url = { workspace = true }
38+
time = "0.3.36"
3039

3140
[dev-dependencies]
3241
proptest = { workspace = true }

src/ic-solana/src/constants.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,6 @@ pub const GET_VOTE_ACCOUNTS_SIZE_ESTIMATE: u64 = 10000;
4040

4141
pub const MAX_GET_BLOCKS_RANGE: u64 = 500_000;
4242
pub const MAX_GET_SLOT_LEADERS: u64 = 5000;
43+
// https://internetcomputer.org/docs/current/references/t-sigs-how-it-works/#fees-for-the-t-schnorr-production-key
44+
// pub const EDDSA_SIGN_COST: u128 = 26_153_846_153;
45+
pub const ECDSA_SIGN_COST: u128 = 26_200_000_000;

src/ic-solana/src/eddsa.rs

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
use std::vec;
2+
3+
use crate::constants::ECDSA_SIGN_COST;
4+
5+
use bip32::Seed;
6+
use candid::Principal;
7+
use candid::{CandidType, Deserialize};
8+
use ic_management_canister_types::{
9+
SchnorrAlgorithm, SchnorrKeyId, SchnorrPublicKeyArgs, SchnorrPublicKeyResult,
10+
SignWithSchnorrArgs, SignWithSchnorrResult,
11+
};
12+
use serde::Serialize;
13+
use serde_bytes::ByteBuf;
14+
use sha2::Digest;
15+
#[derive(CandidType, Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
16+
pub enum KeyType {
17+
ChainKey,
18+
Native(Vec<u8>),
19+
}
20+
21+
/// Fetches the ed25519 public key from the schnorr canister.
22+
pub async fn eddsa_public_key(
23+
key_type: KeyType,
24+
key_name: String,
25+
derivation_path: Vec<ByteBuf>,
26+
) -> Vec<u8> {
27+
match key_type {
28+
KeyType::ChainKey => {
29+
let res: Result<(SchnorrPublicKeyResult,), _> = ic_cdk::call(
30+
Principal::management_canister(),
31+
"schnorr_public_key",
32+
(SchnorrPublicKeyArgs {
33+
canister_id: None,
34+
derivation_path: derivation_path
35+
.iter()
36+
.map(|p| p.clone().into_vec())
37+
.collect(),
38+
key_id: SchnorrKeyId {
39+
algorithm: SchnorrAlgorithm::Ed25519,
40+
name: key_name,
41+
},
42+
},),
43+
)
44+
.await;
45+
46+
res.unwrap().0.public_key
47+
}
48+
KeyType::Native(seed) => {
49+
let derivation_path = derivation_path_ed25519(&ic_cdk::api::id(), &derivation_path);
50+
native_public_key_ed25519(Seed::new(seed.try_into().unwrap()), derivation_path)
51+
}
52+
}
53+
}
54+
55+
fn native_public_key_ed25519(
56+
seed: Seed,
57+
derivation_path: ic_crypto_ed25519::DerivationPath,
58+
) -> Vec<u8> {
59+
let seed_32_bytes =
60+
<[u8; 32]>::try_from(&seed.as_bytes()[0..32]).expect("seed should be >= 32 bytes");
61+
let master_secret = ic_crypto_ed25519::PrivateKey::deserialize_raw_32(&seed_32_bytes);
62+
let (derived_secret, _) = master_secret.derive_subkey(&derivation_path);
63+
let public_key = derived_secret.public_key();
64+
public_key.serialize_raw().to_vec()
65+
}
66+
67+
fn derivation_path_ed25519(
68+
canister_id: &Principal,
69+
derivation_path: &Vec<ByteBuf>,
70+
) -> ic_crypto_ed25519::DerivationPath {
71+
let mut path = vec![];
72+
let derivation_index = ic_crypto_ed25519::DerivationIndex(canister_id.as_slice().to_vec());
73+
path.push(derivation_index);
74+
75+
for index in derivation_path {
76+
path.push(ic_crypto_ed25519::DerivationIndex(index.to_vec()));
77+
}
78+
ic_crypto_ed25519::DerivationPath::new(path)
79+
}
80+
81+
/// Signs a message with an ed25519 key.
82+
pub async fn sign_with_eddsa(
83+
key_type: &KeyType,
84+
key_name: String,
85+
derivation_path: Vec<ByteBuf>,
86+
message: Vec<u8>,
87+
) -> Vec<u8> {
88+
match key_type {
89+
KeyType::ChainKey => {
90+
let res: Result<(SignWithSchnorrResult,), _> = ic_cdk::api::call::call_with_payment(
91+
Principal::management_canister(),
92+
"sign_with_schnorr",
93+
(SignWithSchnorrArgs {
94+
message,
95+
derivation_path: derivation_path
96+
.iter()
97+
.map(|p| p.clone().into_vec())
98+
.collect(),
99+
key_id: SchnorrKeyId {
100+
name: key_name,
101+
algorithm: SchnorrAlgorithm::Ed25519,
102+
},
103+
},),
104+
// https://internetcomputer.org/docs/current/references/t-sigs-how-it-works/#fees-for-the-t-schnorr-production-key
105+
// 26_153_846_153,
106+
ECDSA_SIGN_COST as u64,
107+
)
108+
.await;
109+
110+
res.unwrap().0.signature
111+
}
112+
KeyType::Native(seed) => {
113+
let derivation_path = derivation_path_ed25519(&ic_cdk::api::id(), &derivation_path);
114+
sign_with_native_ed25519(
115+
&Seed::new(seed.as_slice().try_into().unwrap()),
116+
derivation_path,
117+
ByteBuf::from(message),
118+
)
119+
}
120+
}
121+
}
122+
123+
fn sign_with_native_ed25519(
124+
seed: &Seed,
125+
derivation_path: ic_crypto_ed25519::DerivationPath,
126+
message: ByteBuf,
127+
) -> Vec<u8> {
128+
let seed_32_bytes =
129+
<[u8; 32]>::try_from(&seed.as_bytes()[0..32]).expect("seed should be >= 32 bytes");
130+
let master_secret = ic_crypto_ed25519::PrivateKey::deserialize_raw_32(&seed_32_bytes);
131+
let (derived_secret, _chain_code) = master_secret.derive_subkey(&derivation_path);
132+
derived_secret.sign_message(&message).to_vec()
133+
}
134+
135+
pub fn sha256(input: &[u8]) -> [u8; 32] {
136+
let mut hasher = sha2::Sha256::new();
137+
hasher.update(input);
138+
hasher.finalize().into()
139+
}
140+
141+
pub fn hash_with_sha256(input: &str) -> String {
142+
let value = sha256(input.as_bytes());
143+
hex::encode(value)
144+
}
145+
146+
#[cfg(test)]
147+
mod tests {
148+
use super::*;
149+
use crate::types::Pubkey;
150+
151+
#[test]
152+
fn test_sign_and_verify_native_schnorr_ed25519() {
153+
use ed25519_dalek::{Signature, Verifier, VerifyingKey};
154+
155+
// Setup for signing
156+
let test_seed = [1u8; 64];
157+
// Example derivation path for signing
158+
let derivation_path = [vec![1u8; 4]]
159+
.iter()
160+
.map(|v| ByteBuf::from(v.clone()))
161+
.collect();
162+
let derivation_path = derivation_path_ed25519(&Principal::anonymous(), &derivation_path);
163+
164+
let message = b"Test message";
165+
166+
// Call the sign function
167+
let sign_reply = sign_with_native_ed25519(
168+
&Seed::new(test_seed),
169+
derivation_path.clone(),
170+
ByteBuf::from(message.to_vec()),
171+
);
172+
173+
// Setup for verification
174+
let signature = Signature::from_slice(&sign_reply).expect("Invalid signature format");
175+
println!("signature: {:?}", signature);
176+
let public_key_reply = native_public_key_ed25519(Seed::new(test_seed), derivation_path);
177+
let pk = Pubkey::try_from(public_key_reply.to_owned().as_slice())
178+
.map_err(|e| e.to_string())
179+
.unwrap();
180+
println!("public_key: {:?}", pk.to_string());
181+
182+
let raw_public_key = public_key_reply.as_slice();
183+
assert_eq!(raw_public_key.len(), 32);
184+
let mut public_key = [0u8; 32];
185+
public_key.copy_from_slice(raw_public_key);
186+
187+
let public_key = VerifyingKey::from_bytes(&public_key).unwrap();
188+
189+
// Verify the signature
190+
assert!(public_key.verify(message, &signature).is_ok());
191+
}
192+
}

src/ic-solana/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod constants;
2+
pub mod eddsa;
23
pub mod logs;
34
pub mod metrics;
45
pub mod request;

0 commit comments

Comments
 (0)