From 7bac2e07f61d6198bdbf571b9a57380b0223be1c Mon Sep 17 00:00:00 2001 From: isoosiss7 Date: Sun, 21 Mar 2021 20:08:50 -0700 Subject: [PATCH 01/15] enh:add functions --- packages/native-utils/common/src/state.rs | 65 ++++++++++++++++++++--- packages/native-utils/common/src/types.rs | 4 ++ 2 files changed, 63 insertions(+), 6 deletions(-) diff --git a/packages/native-utils/common/src/state.rs b/packages/native-utils/common/src/state.rs index bde560c..6c53532 100644 --- a/packages/native-utils/common/src/state.rs +++ b/packages/native-utils/common/src/state.rs @@ -11,7 +11,7 @@ use super::tokenize::*; use super::types::*; use super::utils::*; -#[derive(Deserialize)] +#[derive(Deserialize,PartialEq)] #[serde(rename_all = "camelCase")] pub struct AllocationItem { pub destination: Bytes32, @@ -39,7 +39,7 @@ impl Tokenize for AssetOutcomeType { } } -#[derive(Deserialize)] +#[derive(Deserialize,PartialEq)] #[serde(rename_all = "camelCase")] pub struct AllocationAssetOutcome { pub asset_holder_address: Address, @@ -55,7 +55,7 @@ impl Tokenize for AllocationAssetOutcome { } } -#[derive(Deserialize, Serialize)] +#[derive(Deserialize, Serialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct Guarantee { pub target_channel_id: Bytes32, @@ -71,7 +71,7 @@ impl Tokenize for Guarantee { } } -#[derive(Deserialize)] +#[derive(Deserialize,PartialEq)] #[serde(rename_all = "camelCase")] pub struct GuaranteeAssetOutcome { pub asset_holder_address: Address, @@ -87,7 +87,7 @@ impl Tokenize for GuaranteeAssetOutcome { } } -#[derive(Deserialize)] +#[derive(Deserialize, PartialEq)] #[serde(untagged)] pub enum AssetOutcome { AllocationAssetOutcome(AllocationAssetOutcome), @@ -107,7 +107,7 @@ impl Tokenize for AssetOutcome { } } -#[derive(Deserialize)] +#[derive(Deserialize, PartialEq)] #[serde(transparent)] pub struct Outcome(Vec); @@ -163,6 +163,11 @@ pub struct State { pub app_data: Bytes, } +pub enum Status { + True, + NeedToCheckApp, +} + impl State { pub fn hash_app_part(&self) -> Bytes32 { keccak256( @@ -219,6 +224,54 @@ impl State { Ok(checksum_address(public_key_to_address(public_key))) } + + + + fn _require_extra_implicit_checks(&self, to_state: &State) -> Result<(), &'static str> { + if &self.turn_num.0 + 1 != to_state.turn_num.0 { + Err("turnNum must increment by one") + } else if self.channel.chain_id != to_state.channel.chain_id { + Err("chainId must not change") + } else if self.channel.channel_nonce != to_state.channel.channel_nonce { + Err("channelNonce must not change") + } else if self.app_definition != to_state.app_definition { + Err("appDefinition must not change") + } else if self.challenge_duration != to_state.challenge_duration { + Err("challengeDuration must not change") + } else { + Ok(()) + } + } + + pub fn require_valid_protocol_transition(self, to_state: State) -> Result { + self._require_extra_implicit_checks(&to_state)?; + + if to_state.is_final { + if self.outcome != to_state.outcome { + Err("Outcome change verboten") + } else { + Ok(Status::True) + } + } else { + if self.is_final { + Err("isFinal retrograde") + } else { + if to_state.turn_num < Uint48(2 * to_state.channel.participants.len() as u64) { + if self.outcome != to_state.outcome { + Err("Outcome change verboten") + } else if self.app_data != to_state.app_data { + Err("appData change forbidden") + } + else { + Ok(Status::True) + } + } + else { + Ok(Status::NeedToCheckApp) + } + } + } + } } #[derive(Serialize)] diff --git a/packages/native-utils/common/src/types.rs b/packages/native-utils/common/src/types.rs index 0f4db93..40efbdc 100644 --- a/packages/native-utils/common/src/types.rs +++ b/packages/native-utils/common/src/types.rs @@ -19,6 +19,7 @@ impl ToHexString for Vec { } } +#[derive(PartialEq)] pub struct Bytes(pub Vec); impl Deref for Bytes { @@ -55,6 +56,7 @@ impl Tokenize for Bytes { } } +#[derive(PartialEq)] pub struct Bytes32(Vec); impl From<[u8; 32]> for Bytes32 { @@ -107,6 +109,7 @@ impl Tokenize for Bytes32 { } } +#[derive(PartialEq, PartialOrd)] pub struct Uint48(pub u64); impl<'de> Deserialize<'de> for Uint48 { @@ -124,6 +127,7 @@ impl Tokenize for Uint48 { } } +#[derive(PartialEq)] pub struct Uint256(pub U256); impl From for Uint256 { From 3c5e49bbf6ab6d666cbecb982168063ae8d8f648 Mon Sep 17 00:00:00 2001 From: isoosiss7 Date: Sun, 28 Mar 2021 17:42:09 -0700 Subject: [PATCH 02/15] enh: refactor - move channel to a file, update signature serde --- packages/native-utils/common/src/channel.rs | 59 +++++++++++++++++++++ packages/native-utils/common/src/lib.rs | 2 + packages/native-utils/common/src/serde.rs | 54 +++++++++++++------ packages/native-utils/common/src/state.rs | 38 +++---------- 4 files changed, 105 insertions(+), 48 deletions(-) create mode 100644 packages/native-utils/common/src/channel.rs diff --git a/packages/native-utils/common/src/channel.rs b/packages/native-utils/common/src/channel.rs new file mode 100644 index 0000000..e15c35f --- /dev/null +++ b/packages/native-utils/common/src/channel.rs @@ -0,0 +1,59 @@ +use std::ops::Deref; + +use ethabi::{encode, Token}; +use ethereum_types::{Address, U256}; +use secp256k1::{recover, sign, Message, RecoveryId, SecretKey, Signature}; +use serde_derive::*; + +use super::encode::*; +use super::tokenize::*; +use super::types::*; +use super::utils::*; +use super::state::*; + +//#[derive(Deserialize)] +//#[serde(rename_all = "camelCase")] +pub struct SignedStateVarsWithHash { + pub turn_num: Uint48, + pub is_final: bool, + pub outcome: Outcome, + pub app_data: Bytes, + pub hash: Bytes32, + pub signature: RecoverableSignature +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Channel { + pub chain_id: Uint256, + pub channel_nonce: Uint256, + pub participants: Vec
, +} + +impl Channel { + pub fn id(&self) -> Bytes32 { + keccak256( + encode(&[ + self.chain_id.tokenize(), + Token::Array( + self.participants + .iter() + .cloned() + .map(Token::Address) + .collect(), + ), + self.channel_nonce.tokenize(), + ]) + .as_slice(), + ) + .into() + } + + pub fn computeNextStatesFromPeerUpdate( + peerState: State, + app_data: Bytes, + + ) { + + } +} \ No newline at end of file diff --git a/packages/native-utils/common/src/lib.rs b/packages/native-utils/common/src/lib.rs index 83e68ab..7a81dd0 100644 --- a/packages/native-utils/common/src/lib.rs +++ b/packages/native-utils/common/src/lib.rs @@ -1,6 +1,7 @@ mod encode; mod serde; mod state; +mod channel; mod tokenize; mod types; mod utils; @@ -11,4 +12,5 @@ pub mod prelude { pub use super::tokenize::Tokenize; pub use super::types::*; pub use super::utils::*; + pub use super::channel::*; } diff --git a/packages/native-utils/common/src/serde.rs b/packages/native-utils/common/src/serde.rs index 8e9de22..4688c45 100644 --- a/packages/native-utils/common/src/serde.rs +++ b/packages/native-utils/common/src/serde.rs @@ -1,25 +1,47 @@ use hex; +use serde::ser::{Serialize, Serializer}; +use serde::de::{Error, Deserialize, Deserializer}; use secp256k1::{RecoveryId, Signature}; -use serde::ser::*; -pub fn serialize_signature( - (signature, recovery_id): &(Signature, RecoveryId), - serializer: S, -) -> Result -where +use super::state::RecoverableSignature; +use super::types::*; + +impl Serialize for RecoverableSignature { + fn serialize( + &self, + serializer: S, + ) -> Result + where S: Serializer, -{ - // A helper struct to go from `[u8; 64]` to `&[u8]` so that `hex::encode` - // accepts it. - struct RawBytes64([u8; 64]); + { + // A helper struct to go from `[u8; 64]` to `&[u8]` so that `hex::encode` + // accepts it. + struct RawBytes64([u8; 64]); - impl AsRef<[u8]> for RawBytes64 { - fn as_ref(&self) -> &[u8] { - &self.0 + impl AsRef<[u8]> for RawBytes64 { + fn as_ref(&self) -> &[u8] { + &self.0 + } } + + let bytes = self.0.serialize(); + let s = hex::encode(RawBytes64(bytes)); + serializer.serialize_str(format!("0x{}{:x}", s, self.1.serialize() + 27).as_str()) } +} - let bytes = signature.serialize(); - let s = hex::encode(RawBytes64(bytes)); - serializer.serialize_str(format!("0x{}{:x}", s, recovery_id.serialize() + 27).as_str()) +impl<'de> Deserialize<'de> for RecoverableSignature { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let bytes: Bytes = Deserialize::deserialize(deserializer)?; + assert_eq!(65,bytes.0.len()); + let mut a: [u8; 64] = [0; 64]; + a.copy_from_slice(&bytes.0[0..65]); + Ok(RecoverableSignature( + Signature::parse(&a), + RecoveryId::parse(bytes.0[64]).map_err(D::Error::custom)? + )) + } } diff --git a/packages/native-utils/common/src/state.rs b/packages/native-utils/common/src/state.rs index 6c53532..8390ca2 100644 --- a/packages/native-utils/common/src/state.rs +++ b/packages/native-utils/common/src/state.rs @@ -6,10 +6,10 @@ use secp256k1::{recover, sign, Message, RecoveryId, SecretKey, Signature}; use serde_derive::*; use super::encode::*; -use super::serde::*; use super::tokenize::*; use super::types::*; use super::utils::*; +use super::channel::*; #[derive(Deserialize,PartialEq)] #[serde(rename_all = "camelCase")] @@ -123,34 +123,6 @@ impl Tokenize for Outcome { } } -#[derive(Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Channel { - pub chain_id: Uint256, - pub channel_nonce: Uint256, - pub participants: Vec
, -} - -impl Channel { - pub fn id(&self) -> Bytes32 { - keccak256( - encode(&[ - self.chain_id.tokenize(), - Token::Array( - self.participants - .iter() - .cloned() - .map(Token::Address) - .collect(), - ), - self.channel_nonce.tokenize(), - ]) - .as_slice(), - ) - .into() - } -} - #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct State { @@ -207,7 +179,7 @@ impl State { Ok(StateSignature { hash, - signature: (signature, recovery_id), + signature: RecoverableSignature(signature, recovery_id), }) } @@ -274,10 +246,12 @@ impl State { } } +pub struct RecoverableSignature(pub Signature, pub RecoveryId); + #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct StateSignature { hash: Bytes32, - #[serde(serialize_with = "serialize_signature")] - signature: (Signature, RecoveryId), + //#[serde(serialize_with = "serialize_signature")] + signature: RecoverableSignature, } From bcda16000efd3e32074f37ee1acc0e00d6cf5974 Mon Sep 17 00:00:00 2001 From: isoosiss7 Date: Sun, 28 Mar 2021 17:48:33 -0700 Subject: [PATCH 03/15] fix: fix typo --- packages/native-utils/common/src/serde.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/native-utils/common/src/serde.rs b/packages/native-utils/common/src/serde.rs index 4688c45..8730813 100644 --- a/packages/native-utils/common/src/serde.rs +++ b/packages/native-utils/common/src/serde.rs @@ -38,10 +38,10 @@ impl<'de> Deserialize<'de> for RecoverableSignature { let bytes: Bytes = Deserialize::deserialize(deserializer)?; assert_eq!(65,bytes.0.len()); let mut a: [u8; 64] = [0; 64]; - a.copy_from_slice(&bytes.0[0..65]); + a.copy_from_slice(&bytes.0[0..64]); Ok(RecoverableSignature( Signature::parse(&a), - RecoveryId::parse(bytes.0[64]).map_err(D::Error::custom)? + RecoveryId::parse(bytes.0[64] - 27).map_err(D::Error::custom)? )) } } From e5dd94481a1a4f7f4c67700d06330c933cb45884 Mon Sep 17 00:00:00 2001 From: isoosiss7 Date: Sun, 28 Mar 2021 20:59:55 -0700 Subject: [PATCH 04/15] enh:add verify --- packages/native-utils/common/src/state.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/packages/native-utils/common/src/state.rs b/packages/native-utils/common/src/state.rs index 8390ca2..15dd3aa 100644 --- a/packages/native-utils/common/src/state.rs +++ b/packages/native-utils/common/src/state.rs @@ -2,7 +2,7 @@ use std::ops::Deref; use ethabi::{encode, Token}; use ethereum_types::{Address, U256}; -use secp256k1::{recover, sign, Message, RecoveryId, SecretKey, Signature}; +use secp256k1::{recover, sign, verify, Message, RecoveryId, SecretKey, Signature}; use serde_derive::*; use super::encode::*; @@ -197,7 +197,16 @@ impl State { Ok(checksum_address(public_key_to_address(public_key))) } - + pub fn verify(self, signature: RecoverableSignature) -> Result { + let hash = self.hash(); + let hashed_message = hash_message(&hash); + let message = Message::parse(&hashed_message); + match recover(&message, &signature.0, &signature.1) + { + Ok(pubkey) => Ok(verify(&message, &signature.0, &pubkey)), + Err(_error) => Ok(false) + } + } fn _require_extra_implicit_checks(&self, to_state: &State) -> Result<(), &'static str> { if &self.turn_num.0 + 1 != to_state.turn_num.0 { @@ -248,6 +257,14 @@ impl State { pub struct RecoverableSignature(pub Signature, pub RecoveryId); +impl RecoverableSignature { + pub fn as_bytes(self) -> Bytes { + let mut v = self.0.serialize().to_vec(); + v.push(self.1.serialize()); + Bytes(v) + } +} + #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct StateSignature { From 7a01e0bf0a29c672bfd3fdc4eee2dd491df47b7a Mon Sep 17 00:00:00 2001 From: isoosiss7 Date: Sun, 4 Apr 2021 11:48:46 -0700 Subject: [PATCH 05/15] Test verify --- packages/native-utils/common/src/serde.rs | 8 +------- packages/native-utils/common/src/state.rs | 10 ++++++++++ packages/native-utils/lib/index.d.ts | 8 ++++++++ packages/native-utils/lib/index.native.js | 3 +++ packages/native-utils/native/src/lib.rs | 4 ++++ packages/native-utils/tests/sign.test.ts | 3 +++ 6 files changed, 29 insertions(+), 7 deletions(-) diff --git a/packages/native-utils/common/src/serde.rs b/packages/native-utils/common/src/serde.rs index 8730813..57693b1 100644 --- a/packages/native-utils/common/src/serde.rs +++ b/packages/native-utils/common/src/serde.rs @@ -36,12 +36,6 @@ impl<'de> Deserialize<'de> for RecoverableSignature { D: Deserializer<'de>, { let bytes: Bytes = Deserialize::deserialize(deserializer)?; - assert_eq!(65,bytes.0.len()); - let mut a: [u8; 64] = [0; 64]; - a.copy_from_slice(&bytes.0[0..64]); - Ok(RecoverableSignature( - Signature::parse(&a), - RecoveryId::parse(bytes.0[64] - 27).map_err(D::Error::custom)? - )) + Ok(RecoverableSignature::from_bytes(bytes).map_err(D::Error::custom)?) } } diff --git a/packages/native-utils/common/src/state.rs b/packages/native-utils/common/src/state.rs index 15dd3aa..4da0bb6 100644 --- a/packages/native-utils/common/src/state.rs +++ b/packages/native-utils/common/src/state.rs @@ -263,6 +263,16 @@ impl RecoverableSignature { v.push(self.1.serialize()); Bytes(v) } + + pub fn from_bytes(bytes: Bytes) -> Result { + assert_eq!(65,bytes.0.len()); + let mut a: [u8; 64] = [0; 64]; + a.copy_from_slice(&bytes.0[0..64]); + Ok(RecoverableSignature( + Signature::parse(&a), + RecoveryId::parse(bytes.0[64] - 27).map_err(|_| "Invalid recovery ID")? + )) + } } #[derive(Serialize)] diff --git a/packages/native-utils/lib/index.d.ts b/packages/native-utils/lib/index.d.ts index 8a8f4f7..a5c1c95 100644 --- a/packages/native-utils/lib/index.d.ts +++ b/packages/native-utils/lib/index.d.ts @@ -79,3 +79,11 @@ export function signState(state: State, privateKey: string): StateWithHashAndSig * @param signature A signature resulting from a previous call to `signState`. */ export function recoverAddress(state: State, signature: string): string + +/** + * Verifies a signature. + * + * @param state A Nitro state. + * @param signature A signature resulting from a previous call to `signState`. + */ + export function verifySignature(state: State, signature: string): boolean diff --git a/packages/native-utils/lib/index.native.js b/packages/native-utils/lib/index.native.js index 7ccaa96..8f9725e 100644 --- a/packages/native-utils/lib/index.native.js +++ b/packages/native-utils/lib/index.native.js @@ -10,6 +10,7 @@ const { signState, recoverAddress, + verifySignature, } = require('../native/index.node') function unwrapResult({ Ok, Err }) { @@ -40,4 +41,6 @@ module.exports = { }, recoverAddress: (state, signature) => unwrapResult(recoverAddress(state, signature)), + + verifySignature: (state, signature) => unwrapResult(verifySignature(state, signature)), } diff --git a/packages/native-utils/native/src/lib.rs b/packages/native-utils/native/src/lib.rs index ed63212..054f437 100644 --- a/packages/native-utils/native/src/lib.rs +++ b/packages/native-utils/native/src/lib.rs @@ -37,4 +37,8 @@ export! { fn recoverAddress(state: State, signature: Bytes) -> Result { state.recover_address(signature) } + + fn verifySignature(state: State, signature: Bytes) -> Result { + state.verify(RecoverableSignature::from_bytes(signature)?) + } } diff --git a/packages/native-utils/tests/sign.test.ts b/packages/native-utils/tests/sign.test.ts index 7122443..91675fe 100644 --- a/packages/native-utils/tests/sign.test.ts +++ b/packages/native-utils/tests/sign.test.ts @@ -99,6 +99,9 @@ describe('Sign state', () => { expect(nativeSignature).toStrictEqual(oldSignature) expect(wasmSignature).toStrictEqual(oldSignature) + + expect(native.verifySignature(state, nativeSignature)).toBe(true) + expect(native.verifySignature(state, wasmSignature)).toBe(true) }) test('Catches invalid private key', async () => { From c056b3d34b57c4c28f659b996a5d5fbff7a51132 Mon Sep 17 00:00:00 2001 From: isoosiss7 Date: Sun, 4 Apr 2021 16:25:15 -0700 Subject: [PATCH 06/15] enh: validate sig with passed in hash --- packages/native-utils/common/Cargo.toml | 2 +- packages/native-utils/common/src/state.rs | 24 ++++++++++++----------- packages/native-utils/lib/index.d.ts | 4 ++-- packages/native-utils/lib/index.native.js | 2 +- packages/native-utils/native/src/lib.rs | 4 ++-- packages/native-utils/tests/sign.test.ts | 17 ++++++++-------- 6 files changed, 28 insertions(+), 25 deletions(-) diff --git a/packages/native-utils/common/Cargo.toml b/packages/native-utils/common/Cargo.toml index 5e61bc7..0d156ff 100644 --- a/packages/native-utils/common/Cargo.toml +++ b/packages/native-utils/common/Cargo.toml @@ -9,7 +9,7 @@ authors = ["Jannis Pohlmann "] ethereum-types = "0.9" ethabi = { git = "https://github.com/graphprotocol/ethabi", rev = "fe7cab5" } hex = "0.4" -libsecp256k1 = "0.3" +libsecp256k1 = "0.3.5" serde_derive = "1.0" serde = "1.0" tiny-keccak = "2.0" diff --git a/packages/native-utils/common/src/state.rs b/packages/native-utils/common/src/state.rs index 4da0bb6..e60267a 100644 --- a/packages/native-utils/common/src/state.rs +++ b/packages/native-utils/common/src/state.rs @@ -197,17 +197,6 @@ impl State { Ok(checksum_address(public_key_to_address(public_key))) } - pub fn verify(self, signature: RecoverableSignature) -> Result { - let hash = self.hash(); - let hashed_message = hash_message(&hash); - let message = Message::parse(&hashed_message); - match recover(&message, &signature.0, &signature.1) - { - Ok(pubkey) => Ok(verify(&message, &signature.0, &pubkey)), - Err(_error) => Ok(false) - } - } - fn _require_extra_implicit_checks(&self, to_state: &State) -> Result<(), &'static str> { if &self.turn_num.0 + 1 != to_state.turn_num.0 { Err("turnNum must increment by one") @@ -255,6 +244,19 @@ impl State { } } +pub fn verify_sig(hash: Bytes32, signature: RecoverableSignature) -> Result { + + let hashed_message = hash_message(&hash); + + let message = Message::parse(&hashed_message); + + match recover(&message, &signature.0, &signature.1) + { + Ok(pubkey) => Ok(verify(&message, &signature.0, &pubkey)), + Err(_error) => Ok(false) + } +} + pub struct RecoverableSignature(pub Signature, pub RecoveryId); impl RecoverableSignature { diff --git a/packages/native-utils/lib/index.d.ts b/packages/native-utils/lib/index.d.ts index a5c1c95..137847c 100644 --- a/packages/native-utils/lib/index.d.ts +++ b/packages/native-utils/lib/index.d.ts @@ -1,4 +1,4 @@ -import { Channel, State } from '@statechannels/nitro-protocol' +import { Bytes32, Channel, State } from '@statechannels/nitro-protocol' /** * A Nitro state with its state hash and signature from signing the state. @@ -86,4 +86,4 @@ export function recoverAddress(state: State, signature: string): string * @param state A Nitro state. * @param signature A signature resulting from a previous call to `signState`. */ - export function verifySignature(state: State, signature: string): boolean + export function verifySignature(hash: Bytes32, signature: string): boolean diff --git a/packages/native-utils/lib/index.native.js b/packages/native-utils/lib/index.native.js index 8f9725e..ddb7828 100644 --- a/packages/native-utils/lib/index.native.js +++ b/packages/native-utils/lib/index.native.js @@ -42,5 +42,5 @@ module.exports = { recoverAddress: (state, signature) => unwrapResult(recoverAddress(state, signature)), - verifySignature: (state, signature) => unwrapResult(verifySignature(state, signature)), + verifySignature: (hash, signature) => unwrapResult(verifySignature(hash, signature)), } diff --git a/packages/native-utils/native/src/lib.rs b/packages/native-utils/native/src/lib.rs index 054f437..7424d58 100644 --- a/packages/native-utils/native/src/lib.rs +++ b/packages/native-utils/native/src/lib.rs @@ -38,7 +38,7 @@ export! { state.recover_address(signature) } - fn verifySignature(state: State, signature: Bytes) -> Result { - state.verify(RecoverableSignature::from_bytes(signature)?) + fn verifySignature(hash: Bytes32, signature: Bytes) -> Result { + verify_sig(hash, RecoverableSignature::from_bytes(signature)?) } } diff --git a/packages/native-utils/tests/sign.test.ts b/packages/native-utils/tests/sign.test.ts index 91675fe..85193fd 100644 --- a/packages/native-utils/tests/sign.test.ts +++ b/packages/native-utils/tests/sign.test.ts @@ -11,7 +11,7 @@ const DEFAULT_STATE: State = { channel: { chainId: '1', channelNonce: 1, - participants: ['0x19E7E376E7C213B7E7e7e46cc70A5dD086DAff2A'], + participants: ['0x63FaC9201494f0bd17B9892B9fae4d52fe3BD377'], }, challengeDuration: 1, outcome: [], @@ -19,7 +19,7 @@ const DEFAULT_STATE: State = { appData: '0x0000000000000000000000000000000000000000000000000000000000000000', } -const PRIVATE_KEY = '0x1111111111111111111111111111111111111111111111111111111111111111' +const PRIVATE_KEY = '0x8da4ef21b864d2cc526dbdb2a120bd2874c36c9d0a1fb7f8c63d7f7a8b41de8f' describe('Hash message', () => { test('Hash a message', async () => { @@ -92,16 +92,17 @@ describe('Sign state', () => { ) // Native - const nativeSignature = native.signState(state, PRIVATE_KEY).signature + const nativeSigned = native.signState(state, PRIVATE_KEY) // WASM - const wasmSignature = wasm.signState(state, PRIVATE_KEY).signature + const wasmSigned = wasm.signState(state, PRIVATE_KEY) - expect(nativeSignature).toStrictEqual(oldSignature) - expect(wasmSignature).toStrictEqual(oldSignature) + expect(nativeSigned.signature).toStrictEqual(oldSignature) + expect(wasmSigned.signature).toStrictEqual(oldSignature) - expect(native.verifySignature(state, nativeSignature)).toBe(true) - expect(native.verifySignature(state, wasmSignature)).toBe(true) + expect(native.verifySignature(nativeSigned.hash, nativeSigned.signature)).toBe(true) + expect(native.verifySignature(wasmSigned.hash, wasmSigned.signature)).toBe(true) + expect(nativeSigned.hash).toStrictEqual(wasmSigned.hash) }) test('Catches invalid private key', async () => { From 33f8af0a449afe1e1c7e1937a7d8e623fabf4e7e Mon Sep 17 00:00:00 2001 From: isoosiss7 Date: Sat, 24 Apr 2021 15:56:56 -0700 Subject: [PATCH 07/15] Completed verifiy signature with tests --- packages/native-utils/common/src/state.rs | 17 ++++++++++------- packages/native-utils/lib/index.d.ts | 2 +- packages/native-utils/lib/index.native.js | 2 +- packages/native-utils/native/src/lib.rs | 4 ++-- packages/native-utils/tests/sign.test.ts | 13 +++++++++++-- 5 files changed, 25 insertions(+), 13 deletions(-) diff --git a/packages/native-utils/common/src/state.rs b/packages/native-utils/common/src/state.rs index e60267a..2bb8f86 100644 --- a/packages/native-utils/common/src/state.rs +++ b/packages/native-utils/common/src/state.rs @@ -244,17 +244,20 @@ impl State { } } -pub fn verify_sig(hash: Bytes32, signature: RecoverableSignature) -> Result { +pub fn verify_sig(hash: Bytes32, address: String, signature: Bytes) -> Result { let hashed_message = hash_message(&hash); - let message = Message::parse(&hashed_message); + let parsed_signature = Signature::parse_slice(&signature[0..signature.len() - 1]) + .or_else(|_| Err("invalid signature length"))?; + let recovery_id = RecoveryId::parse_rpc(signature[signature.len() - 1]) + .or_else(|_| Err("invalid recovery ID"))?; + let public_key = recover(&message, &parsed_signature, &recovery_id) + .or_else(|_| Err("invalid signature"))?; - match recover(&message, &signature.0, &signature.1) - { - Ok(pubkey) => Ok(verify(&message, &signature.0, &pubkey)), - Err(_error) => Ok(false) - } + let recovered_address = checksum_address(public_key_to_address(public_key)); + + Ok(recovered_address.eq(&address)) } pub struct RecoverableSignature(pub Signature, pub RecoveryId); diff --git a/packages/native-utils/lib/index.d.ts b/packages/native-utils/lib/index.d.ts index 137847c..beb3675 100644 --- a/packages/native-utils/lib/index.d.ts +++ b/packages/native-utils/lib/index.d.ts @@ -86,4 +86,4 @@ export function recoverAddress(state: State, signature: string): string * @param state A Nitro state. * @param signature A signature resulting from a previous call to `signState`. */ - export function verifySignature(hash: Bytes32, signature: string): boolean + export function verifySignature(hash: Bytes32, address: string, signature: string): boolean diff --git a/packages/native-utils/lib/index.native.js b/packages/native-utils/lib/index.native.js index ddb7828..797b2b1 100644 --- a/packages/native-utils/lib/index.native.js +++ b/packages/native-utils/lib/index.native.js @@ -42,5 +42,5 @@ module.exports = { recoverAddress: (state, signature) => unwrapResult(recoverAddress(state, signature)), - verifySignature: (hash, signature) => unwrapResult(verifySignature(hash, signature)), + verifySignature: (hash, address, signature) => unwrapResult(verifySignature(hash, address, signature)), } diff --git a/packages/native-utils/native/src/lib.rs b/packages/native-utils/native/src/lib.rs index 7424d58..f12cdfd 100644 --- a/packages/native-utils/native/src/lib.rs +++ b/packages/native-utils/native/src/lib.rs @@ -38,7 +38,7 @@ export! { state.recover_address(signature) } - fn verifySignature(hash: Bytes32, signature: Bytes) -> Result { - verify_sig(hash, RecoverableSignature::from_bytes(signature)?) + fn verifySignature(hash: Bytes32, address: String, signature: Bytes) -> Result { + verify_sig(hash, address, signature) } } diff --git a/packages/native-utils/tests/sign.test.ts b/packages/native-utils/tests/sign.test.ts index 85193fd..609b66a 100644 --- a/packages/native-utils/tests/sign.test.ts +++ b/packages/native-utils/tests/sign.test.ts @@ -100,9 +100,18 @@ describe('Sign state', () => { expect(nativeSigned.signature).toStrictEqual(oldSignature) expect(wasmSigned.signature).toStrictEqual(oldSignature) - expect(native.verifySignature(nativeSigned.hash, nativeSigned.signature)).toBe(true) - expect(native.verifySignature(wasmSigned.hash, wasmSigned.signature)).toBe(true) + // State hash can be verified by signature + expect(native.verifySignature(nativeSigned.hash, state.channel.participants[0], nativeSigned.signature)).toBe(true) + expect(native.verifySignature(wasmSigned.hash, state.channel.participants[0], wasmSigned.signature)).toBe(true) expect(nativeSigned.hash).toStrictEqual(wasmSigned.hash) + + // Old state cannot be verified by new signature + // New state cannto be vierified by old signature + state.turnNum += 1 + const signedNewState = native.signState(state, PRIVATE_KEY) + expect(native.verifySignature(nativeSigned.hash, state.channel.participants[0], signedNewState.signature)).toBe(false) + expect(native.verifySignature(signedNewState.hash, state.channel.participants[0], nativeSigned.signature)).toBe(false) + expect(native.verifySignature(signedNewState.hash, state.channel.participants[0], signedNewState.signature)).toBe(true) }) test('Catches invalid private key', async () => { From d4a8141d2a26e0084da0defe6783bf6414236e74 Mon Sep 17 00:00:00 2001 From: isoosiss7 Date: Sun, 25 Apr 2021 10:04:53 -0700 Subject: [PATCH 08/15] Peer state validate and tests --- packages/native-utils/common/src/state.rs | 25 +++++++-- packages/native-utils/lib/index.d.ts | 9 ++++ packages/native-utils/lib/index.native.js | 3 ++ packages/native-utils/native/src/lib.rs | 4 ++ packages/native-utils/tests/sign.test.ts | 64 +++++++++++++++++++---- 5 files changed, 92 insertions(+), 13 deletions(-) diff --git a/packages/native-utils/common/src/state.rs b/packages/native-utils/common/src/state.rs index 2bb8f86..b0f9e8b 100644 --- a/packages/native-utils/common/src/state.rs +++ b/packages/native-utils/common/src/state.rs @@ -2,7 +2,7 @@ use std::ops::Deref; use ethabi::{encode, Token}; use ethereum_types::{Address, U256}; -use secp256k1::{recover, sign, verify, Message, RecoveryId, SecretKey, Signature}; +use secp256k1::{recover, sign, Message, RecoveryId, SecretKey, Signature}; use serde_derive::*; use super::encode::*; @@ -135,6 +135,7 @@ pub struct State { pub app_data: Bytes, } +#[derive(Serialize)] pub enum Status { True, NeedToCheckApp, @@ -183,7 +184,7 @@ impl State { }) } - pub fn recover_address(self, signature: Bytes) -> Result { + pub fn recover_address(&self, signature: Bytes) -> Result { let hash = self.hash(); let hashed_message = hash_message(&hash); let message = Message::parse(&hashed_message); @@ -197,6 +198,23 @@ impl State { Ok(checksum_address(public_key_to_address(public_key))) } + pub fn validate_peer_update(&self, peer_update: State, peer_signature: Bytes) -> Result { + peer_update.validate_signature(peer_signature)?; + self.require_valid_protocol_transition(peer_update) + } + + fn validate_signature(&self, signature: Bytes) -> Result<(), &'static str> { + let signer_index = ((self.turn_num.0 -1) % self.channel.participants.len() as u64) as usize; + let signer_address = self.channel.participants[signer_index]; + let recovered_address = self.recover_address(signature)?; + + if recovered_address.eq(&checksum_address(signer_address.0.to_vec())) { + Ok(()) + } else { + Err("Signature verification failed") + } + } + fn _require_extra_implicit_checks(&self, to_state: &State) -> Result<(), &'static str> { if &self.turn_num.0 + 1 != to_state.turn_num.0 { Err("turnNum must increment by one") @@ -213,7 +231,7 @@ impl State { } } - pub fn require_valid_protocol_transition(self, to_state: State) -> Result { + pub fn require_valid_protocol_transition(&self, to_state: State) -> Result { self._require_extra_implicit_checks(&to_state)?; if to_state.is_final { @@ -284,6 +302,5 @@ impl RecoverableSignature { #[serde(rename_all = "camelCase")] pub struct StateSignature { hash: Bytes32, - //#[serde(serialize_with = "serialize_signature")] signature: RecoverableSignature, } diff --git a/packages/native-utils/lib/index.d.ts b/packages/native-utils/lib/index.d.ts index beb3675..9b479cd 100644 --- a/packages/native-utils/lib/index.d.ts +++ b/packages/native-utils/lib/index.d.ts @@ -87,3 +87,12 @@ export function recoverAddress(state: State, signature: string): string * @param signature A signature resulting from a previous call to `signState`. */ export function verifySignature(hash: Bytes32, address: string, signature: string): boolean + + /** + * Validate peer update. + * + * @param state A Nitro state. + * @param peer_update Next state suggested by peer + * @param signature Peer's signature for next state. + */ + export function validatePeerUpdate(state, peer_update, signature): string \ No newline at end of file diff --git a/packages/native-utils/lib/index.native.js b/packages/native-utils/lib/index.native.js index 797b2b1..c8ccf9c 100644 --- a/packages/native-utils/lib/index.native.js +++ b/packages/native-utils/lib/index.native.js @@ -11,6 +11,7 @@ const { signState, recoverAddress, verifySignature, + validatePeerUpdate, } = require('../native/index.node') function unwrapResult({ Ok, Err }) { @@ -43,4 +44,6 @@ module.exports = { recoverAddress: (state, signature) => unwrapResult(recoverAddress(state, signature)), verifySignature: (hash, address, signature) => unwrapResult(verifySignature(hash, address, signature)), + + validatePeerUpdate: (state, peer_update, signature) => unwrapResult(validatePeerUpdate(state, peer_update, signature)), } diff --git a/packages/native-utils/native/src/lib.rs b/packages/native-utils/native/src/lib.rs index f12cdfd..80c829e 100644 --- a/packages/native-utils/native/src/lib.rs +++ b/packages/native-utils/native/src/lib.rs @@ -41,4 +41,8 @@ export! { fn verifySignature(hash: Bytes32, address: String, signature: Bytes) -> Result { verify_sig(hash, address, signature) } + + fn validatePeerUpdate(state: State, peer_update: State, peer_signature: Bytes) -> Result { + state.validate_peer_update(peer_update, peer_signature) + } } diff --git a/packages/native-utils/tests/sign.test.ts b/packages/native-utils/tests/sign.test.ts index 609b66a..10beee4 100644 --- a/packages/native-utils/tests/sign.test.ts +++ b/packages/native-utils/tests/sign.test.ts @@ -11,7 +11,7 @@ const DEFAULT_STATE: State = { channel: { chainId: '1', channelNonce: 1, - participants: ['0x63FaC9201494f0bd17B9892B9fae4d52fe3BD377'], + participants: ['0x63FaC9201494f0bd17B9892B9fae4d52fe3BD377', '0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1'], }, challengeDuration: 1, outcome: [], @@ -19,7 +19,8 @@ const DEFAULT_STATE: State = { appData: '0x0000000000000000000000000000000000000000000000000000000000000000', } -const PRIVATE_KEY = '0x8da4ef21b864d2cc526dbdb2a120bd2874c36c9d0a1fb7f8c63d7f7a8b41de8f' +const PRIVATE_KEY1 = '0x8da4ef21b864d2cc526dbdb2a120bd2874c36c9d0a1fb7f8c63d7f7a8b41de8f' +const PRIVATE_KEY2 = '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d' describe('Hash message', () => { test('Hash a message', async () => { @@ -54,14 +55,14 @@ describe('Sign state', () => { outcome, } const oldSignature = utils.joinSignature( - (await nitro.signState(state, PRIVATE_KEY)).signature, + (await nitro.signState(state, PRIVATE_KEY1)).signature, ) // Native - const nativeSignature = native.signState(state, PRIVATE_KEY).signature + const nativeSignature = native.signState(state, PRIVATE_KEY1).signature // WASM - const wasmSignature = wasm.signState(state, PRIVATE_KEY).signature + const wasmSignature = wasm.signState(state, PRIVATE_KEY1).signature expect(nativeSignature).toStrictEqual(oldSignature) expect(wasmSignature).toStrictEqual(oldSignature) @@ -88,14 +89,14 @@ describe('Sign state', () => { } const oldSignature = utils.joinSignature( - (await nitro.signState(state, PRIVATE_KEY)).signature, + (await nitro.signState(state, PRIVATE_KEY1)).signature, ) // Native - const nativeSigned = native.signState(state, PRIVATE_KEY) + const nativeSigned = native.signState(state, PRIVATE_KEY1) // WASM - const wasmSigned = wasm.signState(state, PRIVATE_KEY) + const wasmSigned = wasm.signState(state, PRIVATE_KEY1) expect(nativeSigned.signature).toStrictEqual(oldSignature) expect(wasmSigned.signature).toStrictEqual(oldSignature) @@ -108,12 +109,57 @@ describe('Sign state', () => { // Old state cannot be verified by new signature // New state cannto be vierified by old signature state.turnNum += 1 - const signedNewState = native.signState(state, PRIVATE_KEY) + const signedNewState = native.signState(state, PRIVATE_KEY1) expect(native.verifySignature(nativeSigned.hash, state.channel.participants[0], signedNewState.signature)).toBe(false) expect(native.verifySignature(signedNewState.hash, state.channel.participants[0], nativeSigned.signature)).toBe(false) expect(native.verifySignature(signedNewState.hash, state.channel.participants[0], signedNewState.signature)).toBe(true) }) + test('Peer states validate as expected', async () => { + const outcome = [ + { + assetHolderAddress: '0x0000000000000000000000000000000000000000', + guarantee: { + targetChannelId: + '0x0000000000000000000000000000000000000000000000000000000000000000', + destinations: [ + '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x1111111111111111111111111111111111111111111111111111111111111111', + ], + }, + }, + ] + + const currentState = { + ...DEFAULT_STATE, + outcome, + } + + const peerState = { + ...DEFAULT_STATE, + outcome, + } + + // First 2 updates + currentState.turnNum = 1; + peerState.turnNum = 2; + + const nativeSigned1 = native.signState(peerState, PRIVATE_KEY2); + expect(native.validatePeerUpdate(currentState, peerState, nativeSigned1.signature)).toEqual("True") + + // Additional updates + currentState.turnNum = 3; + peerState.turnNum = 4; + + const nativeSigned2 = native.signState(peerState, PRIVATE_KEY2); + expect(native.validatePeerUpdate(currentState, peerState, nativeSigned2.signature)).toEqual("NeedToCheckApp") + + // Singed by wrong signature + const nativeSigned3 = native.signState(peerState, PRIVATE_KEY1); + expect(() => native.validatePeerUpdate(currentState, peerState, nativeSigned3.signature)).toThrow('Signature verification failed'); + }) + + test('Catches invalid private key', async () => { // Invalid signature length expect(() => native.signState(DEFAULT_STATE, '0x00')).toThrow('invalid private key') From 3d192432f51ecb25d9d784dd4bb0f57fc6ad79be Mon Sep 17 00:00:00 2001 From: isoosiss7 Date: Sun, 25 Apr 2021 10:09:01 -0700 Subject: [PATCH 09/15] Resolve warning messages during compile --- packages/native-utils/common/src/channel.rs | 14 +------------- packages/native-utils/common/src/serde.rs | 1 - 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/packages/native-utils/common/src/channel.rs b/packages/native-utils/common/src/channel.rs index e15c35f..ac703ab 100644 --- a/packages/native-utils/common/src/channel.rs +++ b/packages/native-utils/common/src/channel.rs @@ -1,11 +1,7 @@ -use std::ops::Deref; - use ethabi::{encode, Token}; -use ethereum_types::{Address, U256}; -use secp256k1::{recover, sign, Message, RecoveryId, SecretKey, Signature}; +use ethereum_types::{Address}; use serde_derive::*; -use super::encode::*; use super::tokenize::*; use super::types::*; use super::utils::*; @@ -48,12 +44,4 @@ impl Channel { ) .into() } - - pub fn computeNextStatesFromPeerUpdate( - peerState: State, - app_data: Bytes, - - ) { - - } } \ No newline at end of file diff --git a/packages/native-utils/common/src/serde.rs b/packages/native-utils/common/src/serde.rs index 57693b1..1dc1d58 100644 --- a/packages/native-utils/common/src/serde.rs +++ b/packages/native-utils/common/src/serde.rs @@ -1,7 +1,6 @@ use hex; use serde::ser::{Serialize, Serializer}; use serde::de::{Error, Deserialize, Deserializer}; -use secp256k1::{RecoveryId, Signature}; use super::state::RecoverableSignature; use super::types::*; From d183f8d01f0cc79c138805c36af1453cebb5df62 Mon Sep 17 00:00:00 2001 From: isoosiss7 Date: Sun, 25 Apr 2021 10:56:24 -0700 Subject: [PATCH 10/15] Add to wasm --- packages/native-utils/lib/index.wasm.js | 4 ++++ packages/native-utils/tests/sign.test.ts | 3 +++ wasm-utils/src/lib.rs | 11 ++++++++++- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/native-utils/lib/index.wasm.js b/packages/native-utils/lib/index.wasm.js index c412f1c..e2e9874 100644 --- a/packages/native-utils/lib/index.wasm.js +++ b/packages/native-utils/lib/index.wasm.js @@ -10,6 +10,8 @@ const { signState, recoverAddress, + verifySignature, + validatePeerUpdate, } = require('@statechannels/wasm-utils') module.exports = { @@ -32,4 +34,6 @@ module.exports = { }, recoverAddress, + verifySignature, + validatePeerUpdate, } diff --git a/packages/native-utils/tests/sign.test.ts b/packages/native-utils/tests/sign.test.ts index 10beee4..009e4e3 100644 --- a/packages/native-utils/tests/sign.test.ts +++ b/packages/native-utils/tests/sign.test.ts @@ -146,6 +146,7 @@ describe('Sign state', () => { const nativeSigned1 = native.signState(peerState, PRIVATE_KEY2); expect(native.validatePeerUpdate(currentState, peerState, nativeSigned1.signature)).toEqual("True") + expect(wasm.validatePeerUpdate(currentState, peerState, nativeSigned1.signature)).toEqual("True") // Additional updates currentState.turnNum = 3; @@ -153,10 +154,12 @@ describe('Sign state', () => { const nativeSigned2 = native.signState(peerState, PRIVATE_KEY2); expect(native.validatePeerUpdate(currentState, peerState, nativeSigned2.signature)).toEqual("NeedToCheckApp") + expect(wasm.validatePeerUpdate(currentState, peerState, nativeSigned2.signature)).toEqual("NeedToCheckApp") // Singed by wrong signature const nativeSigned3 = native.signState(peerState, PRIVATE_KEY1); expect(() => native.validatePeerUpdate(currentState, peerState, nativeSigned3.signature)).toThrow('Signature verification failed'); + expect(() => wasm.validatePeerUpdate(currentState, peerState, nativeSigned3.signature)).toThrow('Signature verification failed'); }) diff --git a/wasm-utils/src/lib.rs b/wasm-utils/src/lib.rs index a28cc4f..82777c8 100644 --- a/wasm-utils/src/lib.rs +++ b/wasm-utils/src/lib.rs @@ -1,6 +1,6 @@ use std::ops::Deref; -use js_sys::JsString; +use js_sys::{JsString}; use wasm_bindgen::prelude::*; use statechannels_native_utils_common::prelude::{hash_message as do_hash_message, *}; @@ -80,3 +80,12 @@ pub fn recover_address(state: &JsState, signature: &JsString) -> Result Result { + let state: State = state.into_serde().unwrap(); + let peer_update: State = peer_update.into_serde().unwrap(); + let signature: Bytes = signature.into_serde().unwrap(); + let result = state.validate_peer_update(peer_update, signature).map_err(JsValue::from)?; + Ok(JsValue::from_serde(&result).unwrap().into()) +} \ No newline at end of file From 986d1b9f67e993c470b7220cab701b6278b009b5 Mon Sep 17 00:00:00 2001 From: isoosiss7 Date: Sun, 25 Apr 2021 11:13:11 -0700 Subject: [PATCH 11/15] Remove verifySignature from UT for the time being --- packages/native-utils/tests/sign.test.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/packages/native-utils/tests/sign.test.ts b/packages/native-utils/tests/sign.test.ts index 009e4e3..110752e 100644 --- a/packages/native-utils/tests/sign.test.ts +++ b/packages/native-utils/tests/sign.test.ts @@ -100,19 +100,6 @@ describe('Sign state', () => { expect(nativeSigned.signature).toStrictEqual(oldSignature) expect(wasmSigned.signature).toStrictEqual(oldSignature) - - // State hash can be verified by signature - expect(native.verifySignature(nativeSigned.hash, state.channel.participants[0], nativeSigned.signature)).toBe(true) - expect(native.verifySignature(wasmSigned.hash, state.channel.participants[0], wasmSigned.signature)).toBe(true) - expect(nativeSigned.hash).toStrictEqual(wasmSigned.hash) - - // Old state cannot be verified by new signature - // New state cannto be vierified by old signature - state.turnNum += 1 - const signedNewState = native.signState(state, PRIVATE_KEY1) - expect(native.verifySignature(nativeSigned.hash, state.channel.participants[0], signedNewState.signature)).toBe(false) - expect(native.verifySignature(signedNewState.hash, state.channel.participants[0], nativeSigned.signature)).toBe(false) - expect(native.verifySignature(signedNewState.hash, state.channel.participants[0], signedNewState.signature)).toBe(true) }) test('Peer states validate as expected', async () => { From dec2b7debcf2e6b43272d4fd8e73c5c2d1f6cd07 Mon Sep 17 00:00:00 2001 From: isoosiss7 Date: Sun, 25 Apr 2021 21:32:53 -0700 Subject: [PATCH 12/15] More UT cases --- packages/native-utils/tests/sign.test.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/native-utils/tests/sign.test.ts b/packages/native-utils/tests/sign.test.ts index 110752e..bb82092 100644 --- a/packages/native-utils/tests/sign.test.ts +++ b/packages/native-utils/tests/sign.test.ts @@ -143,10 +143,29 @@ describe('Sign state', () => { expect(native.validatePeerUpdate(currentState, peerState, nativeSigned2.signature)).toEqual("NeedToCheckApp") expect(wasm.validatePeerUpdate(currentState, peerState, nativeSigned2.signature)).toEqual("NeedToCheckApp") - // Singed by wrong signature + // Signer mismatch const nativeSigned3 = native.signState(peerState, PRIVATE_KEY1); expect(() => native.validatePeerUpdate(currentState, peerState, nativeSigned3.signature)).toThrow('Signature verification failed'); expect(() => wasm.validatePeerUpdate(currentState, peerState, nativeSigned3.signature)).toThrow('Signature verification failed'); + + // turn number + currentState.turnNum = 2; + peerState.turnNum = 4; + const nativeSigned4 = native.signState(peerState, PRIVATE_KEY2); + expect(() => native.validatePeerUpdate(currentState, peerState, nativeSigned4.signature)).toThrow('turnNum must increment by one'); + expect(() => wasm.validatePeerUpdate(currentState, peerState, nativeSigned4.signature)).toThrow('turnNum must increment by one'); + + // Nonce + currentState.turnNum = 3; + peerState.turnNum = 4; + peerState.channel = { + chainId: '1', + channelNonce: 2, + participants: ['0x63FaC9201494f0bd17B9892B9fae4d52fe3BD377', '0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1'], + } + const nativeSigned5 = native.signState(peerState, PRIVATE_KEY2); + + expect(() => native.validatePeerUpdate(currentState, peerState, nativeSigned5.signature)).toThrow('channelNonce must not change'); }) From 4efe6f2da225a8d28e49a9ead725d470f291c986 Mon Sep 17 00:00:00 2001 From: isoosiss7 <75552489+isoosiss7@users.noreply.github.com> Date: Sat, 1 May 2021 20:47:04 -0700 Subject: [PATCH 13/15] Update packages/native-utils/common/src/state.rs Co-authored-by: andrewgordstewart --- packages/native-utils/common/src/state.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/native-utils/common/src/state.rs b/packages/native-utils/common/src/state.rs index b0f9e8b..0828ae5 100644 --- a/packages/native-utils/common/src/state.rs +++ b/packages/native-utils/common/src/state.rs @@ -242,7 +242,7 @@ impl State { } } else { if self.is_final { - Err("isFinal retrograde") + Err("transition from a final state to a non-final state") } else { if to_state.turn_num < Uint48(2 * to_state.channel.participants.len() as u64) { if self.outcome != to_state.outcome { From 5de3de8540eba56c2bce44e8dc11a66085083477 Mon Sep 17 00:00:00 2001 From: isoosiss7 Date: Sun, 2 May 2021 09:07:42 -0700 Subject: [PATCH 14/15] Remove unused function and fix bug --- packages/native-utils/common/src/state.rs | 20 ++------------------ packages/native-utils/native/src/lib.rs | 4 ---- 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/packages/native-utils/common/src/state.rs b/packages/native-utils/common/src/state.rs index b0f9e8b..ac8d949 100644 --- a/packages/native-utils/common/src/state.rs +++ b/packages/native-utils/common/src/state.rs @@ -236,7 +236,7 @@ impl State { if to_state.is_final { if self.outcome != to_state.outcome { - Err("Outcome change verboten") + Err("Outcome change forbidden") } else { Ok(Status::True) } @@ -246,7 +246,7 @@ impl State { } else { if to_state.turn_num < Uint48(2 * to_state.channel.participants.len() as u64) { if self.outcome != to_state.outcome { - Err("Outcome change verboten") + Err("Outcome change forbidden") } else if self.app_data != to_state.app_data { Err("appData change forbidden") } @@ -262,22 +262,6 @@ impl State { } } -pub fn verify_sig(hash: Bytes32, address: String, signature: Bytes) -> Result { - - let hashed_message = hash_message(&hash); - let message = Message::parse(&hashed_message); - let parsed_signature = Signature::parse_slice(&signature[0..signature.len() - 1]) - .or_else(|_| Err("invalid signature length"))?; - let recovery_id = RecoveryId::parse_rpc(signature[signature.len() - 1]) - .or_else(|_| Err("invalid recovery ID"))?; - let public_key = recover(&message, &parsed_signature, &recovery_id) - .or_else(|_| Err("invalid signature"))?; - - let recovered_address = checksum_address(public_key_to_address(public_key)); - - Ok(recovered_address.eq(&address)) -} - pub struct RecoverableSignature(pub Signature, pub RecoveryId); impl RecoverableSignature { diff --git a/packages/native-utils/native/src/lib.rs b/packages/native-utils/native/src/lib.rs index 80c829e..e467962 100644 --- a/packages/native-utils/native/src/lib.rs +++ b/packages/native-utils/native/src/lib.rs @@ -38,10 +38,6 @@ export! { state.recover_address(signature) } - fn verifySignature(hash: Bytes32, address: String, signature: Bytes) -> Result { - verify_sig(hash, address, signature) - } - fn validatePeerUpdate(state: State, peer_update: State, peer_signature: Bytes) -> Result { state.validate_peer_update(peer_update, peer_signature) } From 5895ac5bb2c163e1d31ab7796ba7b66e9f3034c2 Mon Sep 17 00:00:00 2001 From: isoosiss7 Date: Sun, 2 May 2021 10:12:45 -0700 Subject: [PATCH 15/15] Refactor test files --- packages/native-utils/tests/sign.test.ts | 67 ------- .../native-utils/tests/transition.test.ts | 172 ++++++++++++++++++ 2 files changed, 172 insertions(+), 67 deletions(-) create mode 100644 packages/native-utils/tests/transition.test.ts diff --git a/packages/native-utils/tests/sign.test.ts b/packages/native-utils/tests/sign.test.ts index bb82092..d0faede 100644 --- a/packages/native-utils/tests/sign.test.ts +++ b/packages/native-utils/tests/sign.test.ts @@ -102,73 +102,6 @@ describe('Sign state', () => { expect(wasmSigned.signature).toStrictEqual(oldSignature) }) - test('Peer states validate as expected', async () => { - const outcome = [ - { - assetHolderAddress: '0x0000000000000000000000000000000000000000', - guarantee: { - targetChannelId: - '0x0000000000000000000000000000000000000000000000000000000000000000', - destinations: [ - '0x0000000000000000000000000000000000000000000000000000000000000000', - '0x1111111111111111111111111111111111111111111111111111111111111111', - ], - }, - }, - ] - - const currentState = { - ...DEFAULT_STATE, - outcome, - } - - const peerState = { - ...DEFAULT_STATE, - outcome, - } - - // First 2 updates - currentState.turnNum = 1; - peerState.turnNum = 2; - - const nativeSigned1 = native.signState(peerState, PRIVATE_KEY2); - expect(native.validatePeerUpdate(currentState, peerState, nativeSigned1.signature)).toEqual("True") - expect(wasm.validatePeerUpdate(currentState, peerState, nativeSigned1.signature)).toEqual("True") - - // Additional updates - currentState.turnNum = 3; - peerState.turnNum = 4; - - const nativeSigned2 = native.signState(peerState, PRIVATE_KEY2); - expect(native.validatePeerUpdate(currentState, peerState, nativeSigned2.signature)).toEqual("NeedToCheckApp") - expect(wasm.validatePeerUpdate(currentState, peerState, nativeSigned2.signature)).toEqual("NeedToCheckApp") - - // Signer mismatch - const nativeSigned3 = native.signState(peerState, PRIVATE_KEY1); - expect(() => native.validatePeerUpdate(currentState, peerState, nativeSigned3.signature)).toThrow('Signature verification failed'); - expect(() => wasm.validatePeerUpdate(currentState, peerState, nativeSigned3.signature)).toThrow('Signature verification failed'); - - // turn number - currentState.turnNum = 2; - peerState.turnNum = 4; - const nativeSigned4 = native.signState(peerState, PRIVATE_KEY2); - expect(() => native.validatePeerUpdate(currentState, peerState, nativeSigned4.signature)).toThrow('turnNum must increment by one'); - expect(() => wasm.validatePeerUpdate(currentState, peerState, nativeSigned4.signature)).toThrow('turnNum must increment by one'); - - // Nonce - currentState.turnNum = 3; - peerState.turnNum = 4; - peerState.channel = { - chainId: '1', - channelNonce: 2, - participants: ['0x63FaC9201494f0bd17B9892B9fae4d52fe3BD377', '0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1'], - } - const nativeSigned5 = native.signState(peerState, PRIVATE_KEY2); - - expect(() => native.validatePeerUpdate(currentState, peerState, nativeSigned5.signature)).toThrow('channelNonce must not change'); - }) - - test('Catches invalid private key', async () => { // Invalid signature length expect(() => native.signState(DEFAULT_STATE, '0x00')).toThrow('invalid private key') diff --git a/packages/native-utils/tests/transition.test.ts b/packages/native-utils/tests/transition.test.ts new file mode 100644 index 0000000..c42731d --- /dev/null +++ b/packages/native-utils/tests/transition.test.ts @@ -0,0 +1,172 @@ +import { State } from '@statechannels/nitro-protocol' +import * as wasm from '@statechannels/wasm-utils' +import * as native from '..' + +const OUTCOME = [ + { + assetHolderAddress: '0x0000000000000000000000000000000000000000', + guarantee: { + targetChannelId: + '0x0000000000000000000000000000000000000000000000000000000000000000', + destinations: [ + '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x1111111111111111111111111111111111111111111111111111111111111111', + ], + }, + }, +] + +const CURRENT_STATE: State = { + turnNum: 5, + isFinal: false, + channel: { + chainId: '1', + channelNonce: 1, + participants: ['0x63FaC9201494f0bd17B9892B9fae4d52fe3BD377', '0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1'], + }, + challengeDuration: 1, + outcome: OUTCOME, + appDefinition: '0x0000000000000000000000000000000000000000', + appData: '0x0000000000000000000000000000000000000000000000000000000000000000', +} + +const NEXT_STATE: State = { + turnNum: 6, + isFinal: false, + channel: { + chainId: '1', + channelNonce: 1, + participants: ['0x63FaC9201494f0bd17B9892B9fae4d52fe3BD377', '0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1'], + }, + challengeDuration: 1, + outcome: OUTCOME, + appDefinition: '0x0000000000000000000000000000000000000000', + appData: '0x0000000000000000000000000000000000000000000000000000000000000000', +} + +const PRIVATE_KEY1 = '0x8da4ef21b864d2cc526dbdb2a120bd2874c36c9d0a1fb7f8c63d7f7a8b41de8f' +const PRIVATE_KEY2 = '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d' + + +describe('Validate state transitions', () => { + test('Pre fund setup passes', async () => { + + let currentState = { + ...CURRENT_STATE, + } + + let peerState = { + ...NEXT_STATE, + } + + // First 2 updates + currentState.turnNum = 1; + peerState.turnNum = 2; + + const nativeSigned = native.signState(peerState, PRIVATE_KEY2); + expect(native.validatePeerUpdate(currentState, peerState, nativeSigned.signature)).toEqual("True") + expect(wasm.validatePeerUpdate(currentState, peerState, nativeSigned.signature)).toEqual("True") + }) + + test('state transition passes', async () => { + let currentState = { + ...CURRENT_STATE, + } + + let peerState = { + ...NEXT_STATE, + } + + currentState.turnNum = 5; + peerState.turnNum = 6; + + const nativeSigned = native.signState(peerState, PRIVATE_KEY2); + expect(native.validatePeerUpdate(currentState, peerState, nativeSigned.signature)).toEqual("NeedToCheckApp") + expect(wasm.validatePeerUpdate(currentState, peerState, nativeSigned.signature)).toEqual("NeedToCheckApp") + + }) + + test('signer mismatch fails', async () => { + let currentState = { + ...CURRENT_STATE, + } + + let peerState = { + ...NEXT_STATE, + } + + const nativeSigned = native.signState(peerState, PRIVATE_KEY1); + expect(() => native.validatePeerUpdate(currentState, peerState, nativeSigned.signature)).toThrow('Signature verification failed'); + expect(() => wasm.validatePeerUpdate(currentState, peerState, nativeSigned.signature)).toThrow('Signature verification failed'); + }); + + test('turn number mismatch fails', async () => { + let currentState = { + ...CURRENT_STATE, + } + + let peerState = { + ...NEXT_STATE, + } + + currentState.turnNum = 4; + peerState.turnNum = 6; + const nativeSigned4 = native.signState(peerState, PRIVATE_KEY2); + expect(() => native.validatePeerUpdate(currentState, peerState, nativeSigned4.signature)).toThrow('turnNum must increment by one'); + expect(() => wasm.validatePeerUpdate(currentState, peerState, nativeSigned4.signature)).toThrow('turnNum must increment by one'); + }); + + test('final transit to non-final fails fails', async () => { + let currentState = { + ...CURRENT_STATE, + } + + let peerState = { + ...NEXT_STATE, + } + + currentState.turnNum = 5; + currentState.isFinal = true; + peerState.turnNum = 6; + peerState.isFinal = false; + const nativeSigned = native.signState(peerState, PRIVATE_KEY2); + expect(() => native.validatePeerUpdate(currentState, peerState, nativeSigned.signature)).toThrow('transition from a final state to a non-final state'); + expect(() => wasm.validatePeerUpdate(currentState, peerState, nativeSigned.signature)).toThrow('transition from a final state to a non-final state'); + }); + + test('Outcome mismatch mismatch fails', async () => { + const otheroutcome = [ + { + assetHolderAddress: '0x0000000000000000000000000000000000000000', + guarantee: { + targetChannelId: + '0x0000000000000000000000000000000000000000000000000000000000000000', + destinations: [ + '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x2222222222222222222222222222222222222222222222220222222222222222', + ], + }, + }, + ] + + let currentState = { + ...CURRENT_STATE, + } + + let peerState = { + ...NEXT_STATE, + } + + currentState.isFinal = true + currentState.turnNum = 5; + + peerState.outcome = otheroutcome + peerState.isFinal = true + peerState.turnNum = 6; + + const nativeSigned = native.signState(peerState, PRIVATE_KEY2); + + expect(() => native.validatePeerUpdate(currentState, peerState, nativeSigned.signature)).toThrow('Outcome change forbidden'); + expect(() => wasm.validatePeerUpdate(currentState, peerState, nativeSigned.signature)).toThrow('Outcome change forbidden'); + }) +})