sp_statement_store/
lib.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18#![cfg_attr(not(feature = "std"), no_std)]
19#![warn(missing_docs)]
20
21//! A crate which contains statement-store primitives.
22
23extern crate alloc;
24
25use alloc::vec::Vec;
26use codec::{Decode, DecodeWithMemTracking, Encode};
27use scale_info::{build::Fields, Path, Type, TypeInfo};
28use sp_application_crypto::RuntimeAppPublic;
29#[cfg(feature = "std")]
30use sp_core::Pair;
31
32/// Statement topic.
33pub type Topic = [u8; 32];
34/// Decryption key identifier.
35pub type DecryptionKey = [u8; 32];
36/// Statement hash.
37pub type Hash = [u8; 32];
38/// Block hash.
39pub type BlockHash = [u8; 32];
40/// Account id
41pub type AccountId = [u8; 32];
42/// Statement channel.
43pub type Channel = [u8; 32];
44
45/// Total number of topic fields allowed.
46pub const MAX_TOPICS: usize = 4;
47
48#[cfg(feature = "std")]
49pub use store_api::{
50	Error, FilterDecision, InvalidReason, RejectionReason, Result, StatementSource, StatementStore,
51	SubmitResult,
52};
53
54#[cfg(feature = "std")]
55mod ecies;
56pub mod runtime_api;
57#[cfg(feature = "std")]
58mod store_api;
59
60mod sr25519 {
61	mod app_sr25519 {
62		use sp_application_crypto::{app_crypto, key_types::STATEMENT, sr25519};
63		app_crypto!(sr25519, STATEMENT);
64	}
65	pub type Public = app_sr25519::Public;
66}
67
68/// Statement-store specific ed25519 crypto primitives.
69pub mod ed25519 {
70	mod app_ed25519 {
71		use sp_application_crypto::{app_crypto, ed25519, key_types::STATEMENT};
72		app_crypto!(ed25519, STATEMENT);
73	}
74	/// Statement-store specific ed25519 public key.
75	pub type Public = app_ed25519::Public;
76	/// Statement-store specific ed25519 key pair.
77	#[cfg(feature = "std")]
78	pub type Pair = app_ed25519::Pair;
79}
80
81mod ecdsa {
82	mod app_ecdsa {
83		use sp_application_crypto::{app_crypto, ecdsa, key_types::STATEMENT};
84		app_crypto!(ecdsa, STATEMENT);
85	}
86	pub type Public = app_ecdsa::Public;
87}
88
89/// Returns blake2-256 hash for the encoded statement.
90#[cfg(feature = "std")]
91pub fn hash_encoded(data: &[u8]) -> [u8; 32] {
92	sp_crypto_hashing::blake2_256(data)
93}
94
95/// Statement proof.
96#[derive(Encode, Decode, DecodeWithMemTracking, TypeInfo, Debug, Clone, PartialEq, Eq)]
97pub enum Proof {
98	/// Sr25519 Signature.
99	Sr25519 {
100		/// Signature.
101		signature: [u8; 64],
102		/// Public key.
103		signer: [u8; 32],
104	},
105	/// Ed25519 Signature.
106	Ed25519 {
107		/// Signature.
108		signature: [u8; 64],
109		/// Public key.
110		signer: [u8; 32],
111	},
112	/// Secp256k1 Signature.
113	Secp256k1Ecdsa {
114		/// Signature.
115		signature: [u8; 65],
116		/// Public key.
117		signer: [u8; 33],
118	},
119	/// On-chain event proof.
120	OnChain {
121		/// Account identifier associated with the event.
122		who: AccountId,
123		/// Hash of block that contains the event.
124		block_hash: BlockHash,
125		/// Index of the event in the event list.
126		event_index: u64,
127	},
128}
129
130impl Proof {
131	/// Return account id for the proof creator.
132	pub fn account_id(&self) -> AccountId {
133		match self {
134			Proof::Sr25519 { signer, .. } => *signer,
135			Proof::Ed25519 { signer, .. } => *signer,
136			Proof::Secp256k1Ecdsa { signer, .. } =>
137				<sp_runtime::traits::BlakeTwo256 as sp_core::Hasher>::hash(signer).into(),
138			Proof::OnChain { who, .. } => *who,
139		}
140	}
141}
142
143/// Statement attributes. Each statement is a list of 0 or more fields. Fields may only appear once
144/// and in the order declared here.
145#[derive(Encode, Decode, TypeInfo, Debug, Clone, PartialEq, Eq)]
146#[repr(u8)]
147pub enum Field {
148	/// Statement proof.
149	AuthenticityProof(Proof) = 0,
150	/// An identifier for the key that `Data` field may be decrypted with.
151	DecryptionKey(DecryptionKey) = 1,
152	/// Priority when competing with other messages from the same sender.
153	Priority(u32) = 2,
154	/// Account channel to use. Only one message per `(account, channel)` pair is allowed.
155	Channel(Channel) = 3,
156	/// First statement topic.
157	Topic1(Topic) = 4,
158	/// Second statement topic.
159	Topic2(Topic) = 5,
160	/// Third statement topic.
161	Topic3(Topic) = 6,
162	/// Fourth statement topic.
163	Topic4(Topic) = 7,
164	/// Additional data.
165	Data(Vec<u8>) = 8,
166}
167
168impl Field {
169	fn discriminant(&self) -> u8 {
170		// This is safe for repr(u8)
171		// see https://doc.rust-lang.org/reference/items/enumerations.html#pointer-casting
172		unsafe { *(self as *const Self as *const u8) }
173	}
174}
175
176/// Statement structure.
177#[derive(DecodeWithMemTracking, Debug, Clone, PartialEq, Eq, Default)]
178pub struct Statement {
179	proof: Option<Proof>,
180	decryption_key: Option<DecryptionKey>,
181	channel: Option<Channel>,
182	priority: Option<u32>,
183	num_topics: u8,
184	topics: [Topic; MAX_TOPICS],
185	data: Option<Vec<u8>>,
186}
187
188/// Note: The `TypeInfo` implementation reflects the actual encoding format (`Vec<Field>`)
189/// rather than the struct fields, since `Statement` has custom `Encode`/`Decode` implementations.
190impl TypeInfo for Statement {
191	type Identity = Self;
192
193	fn type_info() -> Type {
194		// Statement encodes as Vec<Field>, so we report the same type info
195		Type::builder()
196			.path(Path::new("Statement", module_path!()))
197			.docs(&["Statement structure"])
198			.composite(Fields::unnamed().field(|f| f.ty::<Vec<Field>>()))
199	}
200}
201
202impl Decode for Statement {
203	fn decode<I: codec::Input>(input: &mut I) -> core::result::Result<Self, codec::Error> {
204		// Encoding matches that of Vec<Field>. Basically this just means accepting that there
205		// will be a prefix of vector length.
206		let num_fields: codec::Compact<u32> = Decode::decode(input)?;
207		let mut tag = 0;
208		let mut statement = Statement::new();
209		for i in 0..num_fields.into() {
210			let field: Field = Decode::decode(input)?;
211			if i > 0 && field.discriminant() <= tag {
212				return Err("Invalid field order or duplicate fields".into())
213			}
214			tag = field.discriminant();
215			match field {
216				Field::AuthenticityProof(p) => statement.set_proof(p),
217				Field::DecryptionKey(key) => statement.set_decryption_key(key),
218				Field::Priority(p) => statement.set_priority(p),
219				Field::Channel(c) => statement.set_channel(c),
220				Field::Topic1(t) => statement.set_topic(0, t),
221				Field::Topic2(t) => statement.set_topic(1, t),
222				Field::Topic3(t) => statement.set_topic(2, t),
223				Field::Topic4(t) => statement.set_topic(3, t),
224				Field::Data(data) => statement.set_plain_data(data),
225			}
226		}
227		Ok(statement)
228	}
229}
230
231impl Encode for Statement {
232	fn encode(&self) -> Vec<u8> {
233		self.encoded(false)
234	}
235}
236
237#[derive(Clone, Copy, PartialEq, Eq, Debug)]
238/// Result returned by `Statement::verify_signature`
239pub enum SignatureVerificationResult {
240	/// Signature is valid and matches this account id.
241	Valid(AccountId),
242	/// Signature has failed verification.
243	Invalid,
244	/// No signature in the proof or no proof.
245	NoSignature,
246}
247
248impl Statement {
249	/// Create a new empty statement with no proof.
250	pub fn new() -> Statement {
251		Default::default()
252	}
253
254	/// Create a new statement with a proof.
255	pub fn new_with_proof(proof: Proof) -> Statement {
256		let mut statement = Self::new();
257		statement.set_proof(proof);
258		statement
259	}
260
261	/// Sign with a key that matches given public key in the keystore.
262	///
263	/// Returns `true` if signing worked (private key present etc).
264	///
265	/// NOTE: This can only be called from the runtime.
266	pub fn sign_sr25519_public(&mut self, key: &sr25519::Public) -> bool {
267		let to_sign = self.signature_material();
268		if let Some(signature) = key.sign(&to_sign) {
269			let proof = Proof::Sr25519 {
270				signature: signature.into_inner().into(),
271				signer: key.clone().into_inner().into(),
272			};
273			self.set_proof(proof);
274			true
275		} else {
276			false
277		}
278	}
279
280	/// Sign with a given private key and add the signature proof field.
281	#[cfg(feature = "std")]
282	pub fn sign_sr25519_private(&mut self, key: &sp_core::sr25519::Pair) {
283		let to_sign = self.signature_material();
284		let proof =
285			Proof::Sr25519 { signature: key.sign(&to_sign).into(), signer: key.public().into() };
286		self.set_proof(proof);
287	}
288
289	/// Sign with a key that matches given public key in the keystore.
290	///
291	/// Returns `true` if signing worked (private key present etc).
292	///
293	/// NOTE: This can only be called from the runtime.
294	pub fn sign_ed25519_public(&mut self, key: &ed25519::Public) -> bool {
295		let to_sign = self.signature_material();
296		if let Some(signature) = key.sign(&to_sign) {
297			let proof = Proof::Ed25519 {
298				signature: signature.into_inner().into(),
299				signer: key.clone().into_inner().into(),
300			};
301			self.set_proof(proof);
302			true
303		} else {
304			false
305		}
306	}
307
308	/// Sign with a given private key and add the signature proof field.
309	#[cfg(feature = "std")]
310	pub fn sign_ed25519_private(&mut self, key: &sp_core::ed25519::Pair) {
311		let to_sign = self.signature_material();
312		let proof =
313			Proof::Ed25519 { signature: key.sign(&to_sign).into(), signer: key.public().into() };
314		self.set_proof(proof);
315	}
316
317	/// Sign with a key that matches given public key in the keystore.
318	///
319	/// Returns `true` if signing worked (private key present etc).
320	///
321	/// NOTE: This can only be called from the runtime.
322	///
323	/// Returns `true` if signing worked (private key present etc).
324	///
325	/// NOTE: This can only be called from the runtime.
326	pub fn sign_ecdsa_public(&mut self, key: &ecdsa::Public) -> bool {
327		let to_sign = self.signature_material();
328		if let Some(signature) = key.sign(&to_sign) {
329			let proof = Proof::Secp256k1Ecdsa {
330				signature: signature.into_inner().into(),
331				signer: key.clone().into_inner().0,
332			};
333			self.set_proof(proof);
334			true
335		} else {
336			false
337		}
338	}
339
340	/// Sign with a given private key and add the signature proof field.
341	#[cfg(feature = "std")]
342	pub fn sign_ecdsa_private(&mut self, key: &sp_core::ecdsa::Pair) {
343		let to_sign = self.signature_material();
344		let proof =
345			Proof::Secp256k1Ecdsa { signature: key.sign(&to_sign).into(), signer: key.public().0 };
346		self.set_proof(proof);
347	}
348
349	/// Check proof signature, if any.
350	pub fn verify_signature(&self) -> SignatureVerificationResult {
351		use sp_runtime::traits::Verify;
352
353		match self.proof() {
354			Some(Proof::OnChain { .. }) | None => SignatureVerificationResult::NoSignature,
355			Some(Proof::Sr25519 { signature, signer }) => {
356				let to_sign = self.signature_material();
357				let signature = sp_core::sr25519::Signature::from(*signature);
358				let public = sp_core::sr25519::Public::from(*signer);
359				if signature.verify(to_sign.as_slice(), &public) {
360					SignatureVerificationResult::Valid(*signer)
361				} else {
362					SignatureVerificationResult::Invalid
363				}
364			},
365			Some(Proof::Ed25519 { signature, signer }) => {
366				let to_sign = self.signature_material();
367				let signature = sp_core::ed25519::Signature::from(*signature);
368				let public = sp_core::ed25519::Public::from(*signer);
369				if signature.verify(to_sign.as_slice(), &public) {
370					SignatureVerificationResult::Valid(*signer)
371				} else {
372					SignatureVerificationResult::Invalid
373				}
374			},
375			Some(Proof::Secp256k1Ecdsa { signature, signer }) => {
376				let to_sign = self.signature_material();
377				let signature = sp_core::ecdsa::Signature::from(*signature);
378				let public = sp_core::ecdsa::Public::from(*signer);
379				if signature.verify(to_sign.as_slice(), &public) {
380					let sender_hash =
381						<sp_runtime::traits::BlakeTwo256 as sp_core::Hasher>::hash(signer);
382					SignatureVerificationResult::Valid(sender_hash.into())
383				} else {
384					SignatureVerificationResult::Invalid
385				}
386			},
387		}
388	}
389
390	/// Calculate statement hash.
391	#[cfg(feature = "std")]
392	pub fn hash(&self) -> [u8; 32] {
393		self.using_encoded(hash_encoded)
394	}
395
396	/// Returns a topic by topic index.
397	pub fn topic(&self, index: usize) -> Option<Topic> {
398		if index < self.num_topics as usize {
399			Some(self.topics[index])
400		} else {
401			None
402		}
403	}
404
405	/// Returns decryption key if any.
406	pub fn decryption_key(&self) -> Option<DecryptionKey> {
407		self.decryption_key
408	}
409
410	/// Convert to internal data.
411	pub fn into_data(self) -> Option<Vec<u8>> {
412		self.data
413	}
414
415	/// Get a reference to the statement proof, if any.
416	pub fn proof(&self) -> Option<&Proof> {
417		self.proof.as_ref()
418	}
419
420	/// Get proof account id, if any
421	pub fn account_id(&self) -> Option<AccountId> {
422		self.proof.as_ref().map(Proof::account_id)
423	}
424
425	/// Get plain data.
426	pub fn data(&self) -> Option<&Vec<u8>> {
427		self.data.as_ref()
428	}
429
430	/// Get plain data len.
431	pub fn data_len(&self) -> usize {
432		self.data().map_or(0, Vec::len)
433	}
434
435	/// Get channel, if any.
436	pub fn channel(&self) -> Option<Channel> {
437		self.channel
438	}
439
440	/// Get priority, if any.
441	pub fn priority(&self) -> Option<u32> {
442		self.priority
443	}
444
445	/// Return encoded fields that can be signed to construct or verify a proof
446	fn signature_material(&self) -> Vec<u8> {
447		self.encoded(true)
448	}
449
450	/// Remove the proof of this statement.
451	pub fn remove_proof(&mut self) {
452		self.proof = None;
453	}
454
455	/// Set statement proof. Any existing proof is overwritten.
456	pub fn set_proof(&mut self, proof: Proof) {
457		self.proof = Some(proof)
458	}
459
460	/// Set statement priority.
461	pub fn set_priority(&mut self, priority: u32) {
462		self.priority = Some(priority)
463	}
464
465	/// Set statement channel.
466	pub fn set_channel(&mut self, channel: Channel) {
467		self.channel = Some(channel)
468	}
469
470	/// Set topic by index. Does noting if index is over `MAX_TOPICS`.
471	pub fn set_topic(&mut self, index: usize, topic: Topic) {
472		if index < MAX_TOPICS {
473			self.topics[index] = topic;
474			self.num_topics = self.num_topics.max(index as u8 + 1);
475		}
476	}
477
478	/// Set decryption key.
479	pub fn set_decryption_key(&mut self, key: DecryptionKey) {
480		self.decryption_key = Some(key);
481	}
482
483	/// Set unencrypted statement data.
484	pub fn set_plain_data(&mut self, data: Vec<u8>) {
485		self.data = Some(data)
486	}
487
488	fn encoded(&self, for_signing: bool) -> Vec<u8> {
489		// Encoding matches that of Vec<Field>. Basically this just means accepting that there
490		// will be a prefix of vector length.
491		let num_fields = if !for_signing && self.proof.is_some() { 1 } else { 0 } +
492			if self.decryption_key.is_some() { 1 } else { 0 } +
493			if self.priority.is_some() { 1 } else { 0 } +
494			if self.channel.is_some() { 1 } else { 0 } +
495			if self.data.is_some() { 1 } else { 0 } +
496			self.num_topics as u32;
497
498		let mut output = Vec::new();
499		// When encoding signature payload, the length prefix is omitted.
500		// This is so that the signature for encoded statement can potentially be derived without
501		// needing to re-encode the statement.
502		if !for_signing {
503			let compact_len = codec::Compact::<u32>(num_fields);
504			compact_len.encode_to(&mut output);
505
506			if let Some(proof) = &self.proof {
507				0u8.encode_to(&mut output);
508				proof.encode_to(&mut output);
509			}
510		}
511		if let Some(decryption_key) = &self.decryption_key {
512			1u8.encode_to(&mut output);
513			decryption_key.encode_to(&mut output);
514		}
515		if let Some(priority) = &self.priority {
516			2u8.encode_to(&mut output);
517			priority.encode_to(&mut output);
518		}
519		if let Some(channel) = &self.channel {
520			3u8.encode_to(&mut output);
521			channel.encode_to(&mut output);
522		}
523		for t in 0..self.num_topics {
524			(4u8 + t).encode_to(&mut output);
525			self.topics[t as usize].encode_to(&mut output);
526		}
527		if let Some(data) = &self.data {
528			8u8.encode_to(&mut output);
529			data.encode_to(&mut output);
530		}
531		output
532	}
533
534	/// Encrypt give data with given key and store both in the statements.
535	#[cfg(feature = "std")]
536	pub fn encrypt(
537		&mut self,
538		data: &[u8],
539		key: &sp_core::ed25519::Public,
540	) -> core::result::Result<(), ecies::Error> {
541		let encrypted = ecies::encrypt_ed25519(key, data)?;
542		self.data = Some(encrypted);
543		self.decryption_key = Some((*key).into());
544		Ok(())
545	}
546
547	/// Decrypt data (if any) with the given private key.
548	#[cfg(feature = "std")]
549	pub fn decrypt_private(
550		&self,
551		key: &sp_core::ed25519::Pair,
552	) -> core::result::Result<Option<Vec<u8>>, ecies::Error> {
553		self.data.as_ref().map(|d| ecies::decrypt_ed25519(key, d)).transpose()
554	}
555}
556
557#[cfg(test)]
558mod test {
559	use crate::{hash_encoded, Field, Proof, SignatureVerificationResult, Statement};
560	use codec::{Decode, Encode};
561	use scale_info::{MetaType, TypeInfo};
562	use sp_application_crypto::Pair;
563
564	#[test]
565	fn statement_encoding_matches_vec() {
566		let mut statement = Statement::new();
567		assert!(statement.proof().is_none());
568		let proof = Proof::OnChain { who: [42u8; 32], block_hash: [24u8; 32], event_index: 66 };
569
570		let decryption_key = [0xde; 32];
571		let topic1 = [0x01; 32];
572		let topic2 = [0x02; 32];
573		let data = vec![55, 99];
574		let priority = 999;
575		let channel = [0xcc; 32];
576
577		statement.set_proof(proof.clone());
578		statement.set_decryption_key(decryption_key);
579		statement.set_priority(priority);
580		statement.set_channel(channel);
581		statement.set_topic(0, topic1);
582		statement.set_topic(1, topic2);
583		statement.set_plain_data(data.clone());
584
585		statement.set_topic(5, [0x55; 32]);
586		assert_eq!(statement.topic(5), None);
587
588		let fields = vec![
589			Field::AuthenticityProof(proof.clone()),
590			Field::DecryptionKey(decryption_key),
591			Field::Priority(priority),
592			Field::Channel(channel),
593			Field::Topic1(topic1),
594			Field::Topic2(topic2),
595			Field::Data(data.clone()),
596		];
597
598		let encoded = statement.encode();
599		assert_eq!(statement.hash(), hash_encoded(&encoded));
600		assert_eq!(encoded, fields.encode());
601
602		let decoded = Statement::decode(&mut encoded.as_slice()).unwrap();
603		assert_eq!(decoded, statement);
604	}
605
606	#[test]
607	fn decode_checks_fields() {
608		let topic1 = [0x01; 32];
609		let topic2 = [0x02; 32];
610		let priority = 999;
611
612		let fields = vec![
613			Field::Priority(priority),
614			Field::Topic1(topic1),
615			Field::Topic1(topic1),
616			Field::Topic2(topic2),
617		]
618		.encode();
619
620		assert!(Statement::decode(&mut fields.as_slice()).is_err());
621
622		let fields =
623			vec![Field::Topic1(topic1), Field::Priority(priority), Field::Topic2(topic2)].encode();
624
625		assert!(Statement::decode(&mut fields.as_slice()).is_err());
626	}
627
628	#[test]
629	fn sign_and_verify() {
630		let mut statement = Statement::new();
631		statement.set_plain_data(vec![42]);
632
633		let sr25519_kp = sp_core::sr25519::Pair::from_string("//Alice", None).unwrap();
634		let ed25519_kp = sp_core::ed25519::Pair::from_string("//Alice", None).unwrap();
635		let secp256k1_kp = sp_core::ecdsa::Pair::from_string("//Alice", None).unwrap();
636
637		statement.sign_sr25519_private(&sr25519_kp);
638		assert_eq!(
639			statement.verify_signature(),
640			SignatureVerificationResult::Valid(sr25519_kp.public().0)
641		);
642
643		statement.sign_ed25519_private(&ed25519_kp);
644		assert_eq!(
645			statement.verify_signature(),
646			SignatureVerificationResult::Valid(ed25519_kp.public().0)
647		);
648
649		statement.sign_ecdsa_private(&secp256k1_kp);
650		assert_eq!(
651			statement.verify_signature(),
652			SignatureVerificationResult::Valid(sp_crypto_hashing::blake2_256(
653				&secp256k1_kp.public().0
654			))
655		);
656
657		// set an invalid signature
658		statement.set_proof(Proof::Sr25519 { signature: [0u8; 64], signer: [0u8; 32] });
659		assert_eq!(statement.verify_signature(), SignatureVerificationResult::Invalid);
660
661		statement.remove_proof();
662		assert_eq!(statement.verify_signature(), SignatureVerificationResult::NoSignature);
663	}
664
665	#[test]
666	fn encrypt_decrypt() {
667		let mut statement = Statement::new();
668		let (pair, _) = sp_core::ed25519::Pair::generate();
669		let plain = b"test data".to_vec();
670
671		//let sr25519_kp = sp_core::sr25519::Pair::from_string("//Alice", None).unwrap();
672		statement.encrypt(&plain, &pair.public()).unwrap();
673		assert_ne!(plain.as_slice(), statement.data().unwrap().as_slice());
674
675		let decrypted = statement.decrypt_private(&pair).unwrap();
676		assert_eq!(decrypted, Some(plain));
677	}
678
679	#[test]
680	fn statement_type_info_matches_encoding() {
681		// Statement has custom Encode/Decode that encodes as Vec<Field>.
682		// Verify that TypeInfo reflects this by containing a reference to Vec<Field>.
683		let statement_type = Statement::type_info();
684		let vec_field_meta = MetaType::new::<Vec<Field>>();
685
686		// The Statement type should be a composite with one unnamed field of type Vec<Field>
687		match statement_type.type_def {
688			scale_info::TypeDef::Composite(composite) => {
689				assert_eq!(composite.fields.len(), 1, "Statement should have exactly one field");
690				let field = &composite.fields[0];
691				assert!(field.name.is_none(), "Field should be unnamed (newtype pattern)");
692				assert_eq!(field.ty, vec_field_meta, "Statement's inner type should be Vec<Field>");
693			},
694			_ => panic!("Statement TypeInfo should be a Composite"),
695		}
696	}
697}