diff --git a/.changelog/unreleased/improvements/807-smaller-signing.md b/.changelog/unreleased/improvements/807-smaller-signing.md new file mode 100644 index 00000000000..1f58798f83e --- /dev/null +++ b/.changelog/unreleased/improvements/807-smaller-signing.md @@ -0,0 +1,2 @@ +- Sign over the hash of code rather than code in transaction signing. + ([#807](https://github.com/anoma/namada/pull/807)) \ No newline at end of file diff --git a/shared/src/proto/types.rs b/shared/src/proto/types.rs index f2df3600514..f951e8f56d2 100644 --- a/shared/src/proto/types.rs +++ b/shared/src/proto/types.rs @@ -127,6 +127,100 @@ where } } +/// A Tx with its code replaced by a hash salted with the Borsh +/// serialized timestamp of the transaction. This structure will almost +/// certainly be smaller than a Tx, yet in the usual cases it contains +/// enough information to confirm that the Tx is as intended and make a +/// non-malleable signature. +#[derive( + Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema, Hash, +)] +pub struct SigningTx { + pub code_hash: [u8; 32], + pub data: Option>, + pub timestamp: DateTimeUtc, +} + +impl SigningTx { + pub fn hash(&self) -> [u8; 32] { + let timestamp = Some(self.timestamp.into()); + let mut bytes = vec![]; + types::Tx { + code: self.code_hash.to_vec(), + data: self.data.clone(), + timestamp, + } + .encode(&mut bytes) + .expect("encoding a transaction failed"); + hash_tx(&bytes).0 + } + + /// Sign a transaction using [`SignedTxData`]. + pub fn sign(self, keypair: &common::SecretKey) -> Self { + let to_sign = self.hash(); + let sig = common::SigScheme::sign(keypair, to_sign); + let signed = SignedTxData { + data: self.data, + sig, + } + .try_to_vec() + .expect("Encoding transaction data shouldn't fail"); + SigningTx { + code_hash: self.code_hash, + data: Some(signed), + timestamp: self.timestamp, + } + } + + /// Verify that the transaction has been signed by the secret key + /// counterpart of the given public key. + pub fn verify_sig( + &self, + pk: &common::PublicKey, + sig: &common::Signature, + ) -> std::result::Result<(), VerifySigError> { + // Try to get the transaction data from decoded `SignedTxData` + let tx_data = self.data.clone().ok_or(VerifySigError::MissingData)?; + let signed_tx_data = SignedTxData::try_from_slice(&tx_data[..]) + .expect("Decoding transaction data shouldn't fail"); + let data = signed_tx_data.data; + let tx = SigningTx { + code_hash: self.code_hash, + data, + timestamp: self.timestamp, + }; + let signed_data = tx.hash(); + common::SigScheme::verify_signature_raw(pk, &signed_data, sig) + } + + /// Expand this reduced Tx using the supplied code only if the the code + /// hashes to the stored code hash + pub fn expand(self, code: Vec) -> Option { + if hash_tx(&code).0 == self.code_hash { + Some(Tx { + code, + data: self.data, + timestamp: self.timestamp, + }) + } else { + None + } + } +} + +impl From for SigningTx { + fn from(tx: Tx) -> SigningTx { + SigningTx { + code_hash: hash_tx(&tx.code).0, + data: tx.data, + timestamp: tx.timestamp, + } + } +} + +/// A SigningTx but with the full code embedded. This structure will almost +/// certainly be bigger than SigningTxs and contains enough information to +/// execute the transaction. #[derive( Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema, Hash, )] @@ -261,28 +355,20 @@ impl Tx { } pub fn hash(&self) -> [u8; 32] { - hash_tx(&self.to_bytes()).0 + SigningTx::from(self.clone()).hash() } pub fn code_hash(&self) -> [u8; 32] { - hash_tx(&self.code).0 + SigningTx::from(self.clone()).code_hash } /// Sign a transaction using [`SignedTxData`]. pub fn sign(self, keypair: &common::SecretKey) -> Self { - let to_sign = self.hash(); - let sig = common::SigScheme::sign(keypair, to_sign); - let signed = SignedTxData { - data: self.data, - sig, - } - .try_to_vec() - .expect("Encoding transaction data shouldn't fail"); - Tx { - code: self.code, - data: Some(signed), - timestamp: self.timestamp, - } + let code = self.code.clone(); + SigningTx::from(self) + .sign(keypair) + .expand(code) + .expect("code hashes to unexpected value") } /// Verify that the transaction has been signed by the secret key @@ -292,18 +378,7 @@ impl Tx { pk: &common::PublicKey, sig: &common::Signature, ) -> std::result::Result<(), VerifySigError> { - // Try to get the transaction data from decoded `SignedTxData` - let tx_data = self.data.clone().ok_or(VerifySigError::MissingData)?; - let signed_tx_data = SignedTxData::try_from_slice(&tx_data[..]) - .expect("Decoding transaction data shouldn't fail"); - let data = signed_tx_data.data; - let tx = Tx { - code: self.code.clone(), - data, - timestamp: self.timestamp, - }; - let signed_data = tx.hash(); - common::SigScheme::verify_signature_raw(pk, &signed_data, sig) + SigningTx::from(self.clone()).verify_sig(pk, sig) } } diff --git a/wasm/checksums.json b/wasm/checksums.json index b89ff8fb329..b5fdcdc720b 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.27f2a447889b931c9ac5e175fbc19f124277cd677d83e1624a5467ac43f3551f.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.df16d46f67c5fe02392f87cf3dbfabb728f4be506fb2461af051e2a17efff383.wasm", - "tx_ibc.wasm": "tx_ibc.494dda55184d02f1d0061e99f642683b916d6eada6ade291e3be26d0c1385be9.wasm", - "tx_init_account.wasm": "tx_init_account.67805cd6d6b9280d1b907d7a2eec3f3b740c9863aebf4ed655b387c455f44922.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.1011afeb69c550014b4f8f989458af326258fc08b4ddb60067b7485b07eb2a55.wasm", - "tx_init_validator.wasm": "tx_init_validator.0df6c8985b40993b3ad62b2cde6a76aecaeb6282b6916a0c98e44deac38c5089.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.c5d4ddb9a9ce2b5e1f2c3c063e36549a535d383a73e569476c218548392d5ccc.wasm", - "tx_transfer.wasm": "tx_transfer.52f882ed5abe72b456e490e035a09ad220141fc04dd8606e3300c7bc4971aea6.wasm", - "tx_unbond.wasm": "tx_unbond.6b1d54cf6978b55e2318deb1a2a37fd77d810dbabefc0a7422ca21c0ac3f6a61.wasm", - "tx_update_vp.wasm": "tx_update_vp.98cea95bd9191b65d90188c733514843333ae1c653c03c2a317fc641116ff01f.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.d5da43dbe86aaa10ddd509cd451a762c52803af3e196b08cced3cee16feb63a7.wasm", - "tx_withdraw.wasm": "tx_withdraw.b6a713a643f6e5b7a4a8263881c4df7d50656990111ccd02e6596ca5575dbe8b.wasm", - "vp_implicit.wasm": "vp_implicit.5cfa9136bd218e9dcc44276265454a3dcebe81d0a11f2a090d354e3463098481.wasm", - "vp_masp.wasm": "vp_masp.00b2bfeb805847bc5f825b8ec632b6eae77cd64a9c5553b87599ba0756489fa4.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.d4f1f3f54348630ee8d805ac44e6799da193f3a1353728fcafe7634cfc3ca0e4.wasm", - "vp_token.wasm": "vp_token.3f887a991f03a6c447a88307d8e04c569162e126e6016740dd884724bc7b3969.wasm", - "vp_user.wasm": "vp_user.2f63cc6f131760edd0975d41c081288921e76d82883699bbfcc8005980c98aca.wasm", - "vp_validator.wasm": "vp_validator.32701a8c9c331a3814e72bcd406b7ae9de3f250e820ac729145841e2ff802123.wasm" + "tx_bond.wasm": "tx_bond.7235131a24027f3f1b7a23fc6ebd9d5627ba70ae28517eb9124078892aec551c.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.39cc08bafbe7f2b51646605352453fce591da52c4eb4a1d18d4f42a92cde1d04.wasm", + "tx_ibc.wasm": "tx_ibc.260a6a4ef24753abb7466f2d3ca77d3e56a814732dd4ad36e6b78c07ee862c6d.wasm", + "tx_init_account.wasm": "tx_init_account.867a6ecb57949686dd00123492b34cfa79981dc74448057d6d993a3a0b2559bd.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.e63ed208992c36648d62b2ad0b05103e153cc829bd87b31d3fab48ce990fac09.wasm", + "tx_init_validator.wasm": "tx_init_validator.54569d151871eab0ba2ff31691dcea4f37dacc48463b84c0df35d1623c97f108.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.d981228b6193ef1eef36c7f87813c91db445b33d7cf785efb3c55611fb78dc3c.wasm", + "tx_transfer.wasm": "tx_transfer.6433f99399995ef2dda9c96279e2ba4ebcfa0b08c4e4bb01f2759b6d226978f7.wasm", + "tx_unbond.wasm": "tx_unbond.33dc68a3cc75e68fdc8d313b6433a5c7e81e7664f5b8456c9f13e5211a4d4422.wasm", + "tx_update_vp.wasm": "tx_update_vp.4be5efb701dcf603cd51f0f4e5a25db5262d085f8b17c12e377b4b4f8b11716c.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.e721c21aa031613afd43fc92d9330ab383438644b1af1f74c7d99bad2ebea0c7.wasm", + "tx_withdraw.wasm": "tx_withdraw.1b89806a37e9974092f97ed6dd67b464d5d7cfbf63b7bb3197342145c8eeefd4.wasm", + "vp_implicit.wasm": "vp_implicit.8ba8a6af407a1a18778d65d10c04c5384dba1d31d00bc69b529786380af6cdf0.wasm", + "vp_masp.wasm": "vp_masp.9c4e6207009fc716f08c9c65abe9e15dc63fcad97805dfa62f3a9924211fb78f.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.0916d238eacd22e77cd8bfbb3580e89a3f5d9c90857257616023d0f25f7063a2.wasm", + "vp_token.wasm": "vp_token.37b2e0d7c7286ad7f3b69fa09e62d226d7e636b8749333826a4ef1648dee2128.wasm", + "vp_user.wasm": "vp_user.f34a7feb62f7da2b304b00656e89a948df222a4991765d501677c4f16a1cb1b6.wasm", + "vp_validator.wasm": "vp_validator.588c11066f49dd6c9a53b703873d883e7398006e8ede21ea184b348767ec5a7f.wasm" } \ No newline at end of file