chainql_core/
address.rs

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	// FIXME: Soft junctions are used in moonbeam derivation paths, but derivation function is available in
154	// bip32::ExtendedPrivateKey, which moonbeam folks are using instead of substrate apis:
155	// https://github.com/moonbeam-foundation/moonbeam/blob/a395d45c71e0575414ac13fdcb531d877131fde9/node/service/src/chain_spec/mod.rs#L89-L115
156	/// Soft junctions are not supported (I.e /m/0/...), thus you may not be able to derive public key from seeds using them.
157	builtin_ecdsa_seed, builtin_ecdsa_address_seed: Ecdsa;
158	/// Soft junctions are not supported (I.e /m/0/...), thus you may not be able to derive public key from seeds using them.
159	/// Accepts an optional `Ss58Format` argument, but it isn't used, it is only left for consistency, ethereum addresses
160	/// do not use ss58 encoding.
161	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}