forked from Near-Bridge-Lab/btc-bridge
-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathchain_signature.rs
More file actions
214 lines (192 loc) · 7.57 KB
/
chain_signature.rs
File metadata and controls
214 lines (192 loc) · 7.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
use crate::{
env, ext_contract, nano_to_sec, near, require, serde_json, AccountId, Contract, ContractExt,
Event, Gas, Promise, PublicKey, MAX_PUBLIC_KEY_RESULT, MAX_SIGNATURE_RESULT,
};
use bitcoin::ecdsa::Signature;
pub const GAS_FOR_SIGN_CALL: Gas = Gas::from_tgas(50);
pub const GAS_FOR_SIGN_BTC_TRANSACTION_CALL_BACK: Gas = Gas::from_tgas(30);
#[near(serializers = [borsh, json])]
pub struct SignRequest {
pub payload: [u8; 32],
pub path: String,
pub key_version: u32,
}
#[near(serializers = [borsh, json])]
#[derive(Clone)]
#[cfg_attr(not(target_arch = "wasm32"), derive(Debug))]
pub struct BigR {
pub affine_point: String,
}
#[near(serializers = [borsh, json])]
#[derive(Clone)]
#[cfg_attr(not(target_arch = "wasm32"), derive(Debug))]
pub struct S {
pub scalar: String,
}
#[near(serializers = [borsh, json])]
#[derive(Clone)]
#[cfg_attr(not(target_arch = "wasm32"), derive(Debug))]
pub struct SignatureResponse {
pub big_r: BigR,
pub s: S,
pub recovery_id: u8,
}
impl SignatureResponse {
pub fn to_btc_signature(&self) -> Signature {
let r_hex = self.big_r.affine_point[2..].to_string();
let s_hex = self.s.scalar.clone();
let r = hex::decode(r_hex).expect("Invalid r hex");
let s = hex::decode(s_hex).expect("Invalid s hex");
let signature = bitcoin::secp256k1::ecdsa::Signature::from_compact(&[r, s].concat())
.expect("Invalid signature");
Signature::sighash_all(signature)
}
}
#[near(serializers=[json])]
pub struct DomainId(pub u64);
#[ext_contract(ext_chain_signatures)]
pub trait ChainSignatures {
fn sign(&mut self, request: SignRequest) -> Promise;
fn public_key(&self, domain_id: Option<DomainId>) -> PublicKey;
}
impl Contract {
pub fn sign_promise(&self, request: SignRequest) -> Promise {
let config = self.internal_config();
ext_chain_signatures::ext(config.chain_signatures_account_id.clone())
.with_static_gas(GAS_FOR_SIGN_CALL)
.with_attached_deposit(env::attached_deposit())
.sign(request)
}
pub fn sync_chain_signatures_root_public_key_promise(&mut self) -> Promise {
ext_chain_signatures::ext(self.internal_config().chain_signatures_account_id.clone())
.public_key(None)
.then(Self::ext(env::current_account_id()).sync_root_public_key_callback())
}
pub fn internal_sign_btc_transaction(
&mut self,
btc_pending_sign_id: String,
sign_index: usize,
key_version: u32,
) -> Promise {
let pending_info = self.internal_unwrap_btc_pending_info(&btc_pending_sign_id);
let public_keys: Vec<_> = pending_info
.vutxos
.iter()
.map(|vutxo| self.generate_btc_public_key(&vutxo.get_path()))
.collect();
let btc_pending_info = self.internal_unwrap_btc_pending_info(&btc_pending_sign_id);
require!(
btc_pending_info.signatures[sign_index].is_none(),
"Already signed"
);
let payload = btc_pending_info
.get_psbt()
.get_hash_to_sign(sign_index, &public_keys);
let path = btc_pending_info.vutxos[sign_index].get_path();
self.sign_promise(SignRequest {
payload,
path,
key_version,
})
.then(
Self::ext(env::current_account_id())
.with_static_gas(GAS_FOR_SIGN_BTC_TRANSACTION_CALL_BACK)
.sign_btc_transaction_callback(
btc_pending_info.account_id.clone(),
btc_pending_sign_id,
sign_index,
),
)
}
}
#[near]
impl Contract {
#[private]
pub fn sync_root_public_key_callback(&mut self) -> bool {
if let Ok(result_bytes) = env::promise_result_checked(0, MAX_PUBLIC_KEY_RESULT) {
let root_public_key =
serde_json::from_slice::<PublicKey>(&result_bytes).expect("Invalid PublicKey");
self.internal_mut_config().chain_signatures_root_public_key = Some(root_public_key);
let change_address = self
.generate_utxo_chain_address(env::current_account_id().as_str())
.to_string();
self.internal_mut_config().change_address = Some(change_address);
true
} else {
false
}
}
#[private]
pub fn sign_btc_transaction_callback(
&mut self,
account_id: AccountId,
btc_pending_sign_id: String,
sign_index: usize,
) -> bool {
if let Ok(result_bytes) = env::promise_result_checked(0, MAX_SIGNATURE_RESULT) {
let signature = serde_json::from_slice::<SignatureResponse>(&result_bytes)
.expect("Invalid signature");
let public_key = self
.generate_btc_public_key(
&self
.internal_unwrap_btc_pending_info(&btc_pending_sign_id)
.vutxos[sign_index]
.get_path(),
)
.inner;
let btc_pending_info = self.internal_unwrap_mut_btc_pending_info(&btc_pending_sign_id);
require!(
btc_pending_info.signatures[sign_index].is_none(),
"Already signed"
);
btc_pending_info.signatures[sign_index] = Some(signature.clone());
btc_pending_info.last_sign_time_sec = nano_to_sec(env::block_timestamp());
Event::BtcInputSignature {
account_id: &account_id,
btc_pending_id: &btc_pending_sign_id,
sign_index,
signature: &signature,
}
.emit();
let mut psbt = btc_pending_info.get_psbt();
psbt.save_signature(sign_index, signature, public_key);
btc_pending_info.psbt_hex = psbt.serialize();
if btc_pending_info.is_all_signed() {
let tx_bytes_with_sign = psbt.extract_tx_bytes_with_sign();
// For ZCash chains, use base64 encoding to save space (1.33x vs 2x overhead for hex)
// ZCash transactions with Orchard bundles are larger and benefit from compact encoding
// For Bitcoin chains, keep hex encoding for backward compatibility
#[cfg(feature = "zcash")]
let tx_bytes_base64 = {
use near_sdk::base64::{engine::general_purpose::STANDARD, Engine};
STANDARD.encode(&tx_bytes_with_sign)
};
Event::SignedBtcTransaction {
account_id: &account_id,
tx_id: btc_pending_sign_id.clone(),
#[cfg(not(feature = "zcash"))]
tx_bytes: &tx_bytes_with_sign,
#[cfg(feature = "zcash")]
tx_bytes_base64,
}
.emit();
btc_pending_info.tx_bytes_with_sign = Some(tx_bytes_with_sign);
btc_pending_info.to_pending_verify_stage();
let is_original_tx = btc_pending_info.get_original_tx_id().is_none();
let account = self.internal_unwrap_mut_account(&account_id);
require!(
account.btc_pending_sign_ids.remove(&btc_pending_sign_id),
"Internal error"
);
if is_original_tx {
account
.btc_pending_verify_list
.insert(btc_pending_sign_id.clone());
}
}
true
} else {
false
}
}
}