1use std::str::FromStr;
2
3use jrsonnet_evaluator::{
4 bail, jrsonnet_macros::builtin, runtime_error, typed::{CheckType, ComplexValType, Either2, Typed, ValType}, val::NumValue, Either, IStr, ObjValue, Result, ResultExt, Val
5};
6use sp_core::{
7 crypto::{SecretStringError, Ss58AddressFormat, Ss58AddressFormatRegistry, Ss58Codec},
8 ecdsa, ed25519, keccak_256, sr25519, Pair,
9};
10use sp_runtime::{traits::Verify, AccountId32, MultiSignature};
11
12use crate::{ensure, ethereum::eth_cksum_address_from_ecdsa, hex::Hex};
13
14pub struct Ss58Format(pub Ss58AddressFormat);
15impl Typed for Ss58Format {
16 const TYPE: &'static ComplexValType = &ComplexValType::UnionRef(&[u16::TYPE, String::TYPE]);
17
18 fn into_untyped(typed: Self) -> Result<Val> {
19 match Ss58AddressFormatRegistry::try_from(typed.0) {
20 Ok(r) => Ok(Val::string(r.to_string())),
21 Err(_e) => Ok(Val::Num(NumValue::new(typed.0.prefix() as f64).unwrap())),
22 }
23 }
24
25 fn from_untyped(untyped: Val) -> Result<Self> {
26 let either = <Either![u16, String]>::from_untyped(untyped)?;
27 match either {
28 Either2::A(id) => Ok(Self(Ss58AddressFormat::custom(id))),
29 Either2::B(name) => match Ss58AddressFormatRegistry::from_str(&name) {
30 Ok(v) => Ok(Self(v.into())),
31 Err(e) => bail!("unsupported address format: {e}"),
32 },
33 }
34 }
35}
36impl Default for Ss58Format {
37 fn default() -> Self {
38 Self(Ss58AddressFormatRegistry::SubstrateAccount.into())
39 }
40}
41
42#[derive(Debug, Clone, Copy)]
43pub enum SignatureSchema {
44 Ed25519,
45 Sr25519,
46 Ecdsa,
47 Ethereum,
48}
49impl Typed for SignatureSchema {
50 const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Str);
51
52 fn into_untyped(typed: Self) -> jrsonnet_evaluator::Result<Val> {
53 Ok(Val::Str(format!("{typed:?}").into()))
54 }
55
56 fn from_untyped(untyped: Val) -> jrsonnet_evaluator::Result<Self> {
57 Self::TYPE.check(&untyped)?;
58 Ok(match untyped.as_str().unwrap().as_str() {
59 "Ed25519" => Self::Ed25519,
60 "Sr25519" => Self::Sr25519,
61 "Ecdsa" => Self::Ecdsa,
62 "Ethereum" => Self::Ethereum,
63 v => bail!("unknown key schema: {}", v),
64 })
65 }
66}
67
68pub fn address_seed(
69 scheme: SignatureSchema,
70 suri: &str,
71 format: Ss58AddressFormat,
72) -> Result<String, SecretStringError> {
73 Ok(match scheme {
74 SignatureSchema::Ed25519 => ed25519::Pair::from_string_with_seed(suri, None)?
75 .0
76 .public()
77 .to_ss58check_with_version(format),
78 SignatureSchema::Sr25519 => sp_core::sr25519::Pair::from_string_with_seed(suri, None)?
79 .0
80 .public()
81 .to_ss58check_with_version(format),
82 SignatureSchema::Ecdsa => sp_core::ecdsa::Pair::from_string_with_seed(suri, None)?
83 .0
84 .public()
85 .to_ss58check_with_version(format),
86 SignatureSchema::Ethereum => {
87 let (pair, _) = sp_core::ecdsa::Pair::from_string_with_seed(suri, None)?;
88 eth_cksum_address_from_ecdsa(pair.public().0)
89 .map_err(|_e| SecretStringError::InvalidSeed)?
90 }
91 })
92}
93pub fn public_bytes_seed(
94 scheme: SignatureSchema,
95 suri: &str,
96) -> Result<Vec<u8>, SecretStringError> {
97 let bytes = match scheme {
98 SignatureSchema::Ed25519 => ed25519::Pair::from_string_with_seed(suri, None)?
99 .0
100 .public()
101 .0
102 .to_vec(),
103 SignatureSchema::Sr25519 => sp_core::sr25519::Pair::from_string_with_seed(suri, None)?
104 .0
105 .public()
106 .0
107 .to_vec(),
108 SignatureSchema::Ecdsa | SignatureSchema::Ethereum => {
109 sp_core::ecdsa::Pair::from_string_with_seed(suri, None)?
110 .0
111 .public()
112 .0
113 .to_vec()
114 }
115 };
116 Ok(bytes)
117}
118
119#[builtin]
120pub fn builtin_seed(scheme: SignatureSchema, suri: IStr) -> Result<Hex> {
121 public_bytes_seed(scheme, &suri)
122 .map_err(|e| runtime_error!("invalid seed: {e}"))
123 .map(Hex)
124}
125
126#[builtin]
127pub fn builtin_address_seed(
128 scheme: SignatureSchema,
129 suri: IStr,
130 format: Option<Ss58Format>,
131) -> Result<String> {
132 address_seed(scheme, &suri, format.unwrap_or_default().0)
133 .map_err(|e| runtime_error!("invalid seed: {e}"))
134}
135
136macro_rules! seed_helpers {
137 ($($(#[$attr:meta])* $fnname:ident, $fname2:ident: $schema:ident);* $(;)?) => {$(
138 $(#[$attr])*
139 #[builtin]
140 pub fn $fnname(v: IStr) -> Result<Hex> {
141 builtin_seed(SignatureSchema::$schema, v)
142 }
143 $(#[$attr])*
144 #[builtin]
145 pub fn $fname2(v: IStr, format: Option<Ss58Format>) -> Result<String> {
146 builtin_address_seed(SignatureSchema::$schema, v, format)
147 }
148 )*};
149}
150seed_helpers! {
151 builtin_sr25519_seed, builtin_sr25519_address_seed: Sr25519;
152 builtin_ed25519_seed, builtin_ed25519_address_seed: Sr25519;
153 builtin_ecdsa_seed, builtin_ecdsa_address_seed: Ecdsa;
158 builtin_ethereum_seed, builtin_ethereum_address_seed: Ethereum;
162}
163
164#[derive(Clone, Copy)]
165pub enum SignatureType {
166 MultiSignature,
167 Plain(SignatureSchema),
168}
169#[derive(Clone, PartialEq)]
170pub enum AddressSchema {
171 Id32,
172 Id20,
173}
174#[derive(Clone)]
175pub enum AddressType {
176 MultiAddress { default: AddressSchema },
177 Plain(AddressSchema),
178}
179pub fn verify_signature(
180 signature_ty: SignatureType,
181 signature: Val,
182 address_ty: AddressType,
183 address: Val,
184 data: Hex,
185) -> Result<bool> {
186 let (signature_ty, signature) = match signature_ty {
187 SignatureType::MultiSignature => {
188 let sign = ObjValue::from_untyped(signature).description("multisignature obj")?;
189 let fields = sign.fields(false);
190 if fields.len() != 1 {
191 bail!("multisignature is enum");
192 };
193 let plain = match fields[0].as_str() {
194 "Ed25519" => SignatureSchema::Ed25519,
195 "Sr25519" => SignatureSchema::Sr25519,
196 "Ecdsa" => SignatureSchema::Ecdsa,
197 s => bail!("unknown multisignature type: {s}"),
198 };
199 let value = sign
200 .get(fields[0].clone())
201 .description("signature value")?
202 .expect("exists");
203 (
204 plain,
205 Hex::from_untyped(value).description("multisig value")?.0,
206 )
207 }
208 SignatureType::Plain(s) => (s, Hex::from_untyped(signature).description("signature")?.0),
209 };
210
211 let (address_ty, address) = match address_ty {
212 AddressType::MultiAddress { default } => {
213 let addr = ObjValue::from_untyped(address).description("multiaddress obj")?;
214 let fields = addr.fields(false);
215 if fields.len() != 1 {
216 bail!("multiaddress is enum");
217 };
218 let ty = fields[0].as_str();
219 if ty == "Address32" || ty == "Id" && default == AddressSchema::Id32 {
220 let value = addr
221 .get(ty.into())
222 .description("address value")?
223 .expect("exists");
224 (
225 AddressSchema::Id32,
226 Hex::from_untyped(value).description("multiaddr value")?.0,
227 )
228 } else {
229 bail!("unknown multiaddress type: {ty}");
230 }
231 }
232 AddressType::Plain(_) => todo!(),
233 };
234
235 if let SignatureSchema::Ethereum = signature_ty {
236 ensure!(
237 address_ty == AddressSchema::Id20,
238 "ethereum address schema should be id20"
239 );
240 let address: [u8; 20] = address
241 .try_into()
242 .map_err(|e| runtime_error!("bad ethereum address: {e:?}"))?;
243 let hash = keccak_256(&data);
244 let ecdsa_sign = signature
245 .try_into()
246 .map_err(|e| runtime_error!("bad ethereum signature: {e:?}"))?;
247 match sp_io::crypto::secp256k1_ecdsa_recover(&ecdsa_sign, &hash) {
248 Ok(pubkey) => {
249 let pubkey_hash = keccak_256(&pubkey);
250 let mut expected_address = [0; 20];
251 expected_address.copy_from_slice(&pubkey_hash[12..32]);
252 return Ok(expected_address == address);
253 }
254 Err(_) => return Ok(false),
255 }
256 }
257
258 let multisig = match signature_ty {
259 SignatureSchema::Ed25519 => MultiSignature::Ed25519(ed25519::Signature::from_raw(
260 signature
261 .try_into()
262 .map_err(|e| runtime_error!("bad ecdsa signature: {e:?}"))?,
263 )),
264 SignatureSchema::Sr25519 => MultiSignature::Sr25519(sr25519::Signature::from_raw(
265 signature
266 .try_into()
267 .map_err(|e| runtime_error!("bad sr25519 signature: {e:?}"))?,
268 )),
269 SignatureSchema::Ecdsa => MultiSignature::Ecdsa(ecdsa::Signature::from_raw(
270 signature
271 .try_into()
272 .map_err(|e| runtime_error!("bad ecdsa signature: {e:?}"))?,
273 )),
274 SignatureSchema::Ethereum => unreachable!("has special handling"),
275 };
276
277 ensure!(
278 address_ty == AddressSchema::Id32,
279 "substrate address schema should be id32"
280 );
281 let address: [u8; 32] = address
282 .try_into()
283 .map_err(|e| runtime_error!("bad accountid32: {e:?}"))?;
284 let id32 = AccountId32::new(address);
285 Ok(multisig.verify(data.0.as_slice(), &id32))
286}