WIP: feat-shamir (old) #103

Draft
CleverWild wants to merge 66 commits from feat-shamir into main
6 changed files with 65 additions and 29 deletions
Showing only changes of commit 8421a09d9f - Show all commits

View File

@@ -314,9 +314,17 @@ create table if not exists proposal_vote (
) STRICT;
create table if not exists proposal_result (
proposal_id integer not null primary key references proposal(id) on delete cascade,
data blob not null,
-- The signature the vault produced for an approved transaction, by component.
--
-- secp256k1 signatures have three common encodings (Electrum v=27/28, raw parity,
-- ERC-2098 compact); a single blob would not say which one it holds. `y_parity` is
-- the raw bit -- add 27 to rebuild the Electrum form `Signature::as_bytes` emits.
create table if not exists proposal_one_off_transaction_result (
proposal_id integer not null primary key
references proposal_one_off_transaction (proposal_id) on delete cascade,
r blob not null check (length(r) = 32),
s blob not null check (length(s) = 32),
y_parity integer not null check (y_parity in (0, 1)),
created_at integer not null default(unixepoch('now'))
) STRICT;

View File

@@ -661,7 +661,6 @@ impl ProposalManager {
tx: one_off_transaction::Settings,
) -> Result<(), Error> {
use crate::actors::evm::ClientSignTransaction;
use crate::db::models::NewProposalResult;
use alloy::{
consensus::TxEip1559,
eips::eip2930::AccessList,
@@ -691,12 +690,7 @@ impl ProposalManager {
.map_err(|e| Error::ExecutionFailed(format!("sign one-off tx: {e}")))?;
let mut conn = self.db.get().await.map_err(Error::DatabaseConnection)?;
diesel::insert_into(schema::proposal_result::table)
.values(NewProposalResult {
proposal_id,
data: sig.as_bytes().to_vec(),
})
.execute(&mut conn)
one_off_transaction::store_signature(proposal_id, &sig, &mut conn)
.await
.map_err(|e| Error::ExecutionFailed(format!("store proposal result: {e}")))?;

View File

@@ -515,13 +515,6 @@ pub struct NewProposalVote {
pub signature: Vec<u8>,
}
#[derive(Debug, Insertable)]
#[diesel(table_name = schema::proposal_result, check_for_backend(Sqlite))]
pub struct NewProposalResult {
pub proposal_id: i32,
pub data: Vec<u8>,
}
#[derive(Debug, Insertable)]
#[diesel(table_name = schema::recovery_proposal_vote, check_for_backend(Sqlite))]
pub struct NewRecoveryProposalVote {

View File

@@ -1,7 +1,10 @@
//! Signing a single EIP-1559 transaction.
use super::{Proposal, ProposalKindTag, as_i64, as_u64, fixed};
use crate::db::{DatabaseConnection, schema::proposal_one_off_transaction};
use crate::db::{
DatabaseConnection,
schema::{proposal_one_off_transaction, proposal_one_off_transaction_result},
};
use diesel::{
Insertable, QueryDsl as _, QueryResult, Queryable, Selectable, SelectableHelper as _,
sqlite::Sqlite,
@@ -100,3 +103,32 @@ impl Proposal for OneOffTransaction {
row.into_settings()
}
}
/// The signature the vault produced for an approved transaction.
#[derive(Debug, Insertable)]
#[diesel(table_name = proposal_one_off_transaction_result, check_for_backend(Sqlite))]
struct SignatureRow {
proposal_id: i32,
r: Vec<u8>,
s: Vec<u8>,
y_parity: i32,
}
/// Records the signature produced for an approved transaction, by component, so what
/// came back is as readable as what was signed.
pub async fn store_signature(
proposal_id: i32,
signature: &alloy::signers::Signature,
conn: &mut DatabaseConnection,
) -> QueryResult<()> {
diesel::insert_into(proposal_one_off_transaction_result::table)
.values(&SignatureRow {
proposal_id,
r: signature.r().to_be_bytes::<32>().to_vec(),
s: signature.s().to_be_bytes::<32>().to_vec(),
y_parity: i32::from(signature.v()),
})
.execute(conn)
.await
.map(drop)
}

View File

@@ -270,9 +270,11 @@ diesel::table! {
}
diesel::table! {
proposal_result (proposal_id) {
proposal_one_off_transaction_result (proposal_id) {
proposal_id -> Integer,
data -> Binary,
r -> Binary,
s -> Binary,
y_parity -> Integer,
created_at -> Integer,
}
}
@@ -383,7 +385,7 @@ diesel::joinable!(evm_wallet_access -> program_client (client_id));
diesel::joinable!(operator -> operator_identity (id));
diesel::joinable!(program_client -> client_metadata (metadata_id));
diesel::joinable!(proposal -> operator_identity (initiator_id));
diesel::joinable!(proposal_result -> proposal (proposal_id));
diesel::joinable!(proposal_one_off_transaction_result -> proposal_one_off_transaction (proposal_id));
diesel::joinable!(proposal_approve_sdk_client -> proposal (proposal_id));
diesel::joinable!(proposal_grant_wallet_access -> proposal (proposal_id));
diesel::joinable!(proposal_replace_operator -> proposal (proposal_id));
@@ -400,7 +402,7 @@ diesel::joinable!(recovery_wakeup_request -> operator_identity (requested_by));
diesel::allow_tables_to_appear_in_same_query!(
aead_encrypted,
proposal_result,
proposal_one_off_transaction_result,
proposal_approve_sdk_client,
proposal_grant_wallet_access,
proposal_replace_operator,

View File

@@ -19,7 +19,7 @@ use arbiter_server::{
use arbiter_server::actors::vault::Bootstrap;
use arbiter_server::db::schema::{
aead_encrypted, evm_basic_grant, evm_wallet, evm_wallet_access, operator_identity,
proposal_result, recovery_operator_identity,
proposal_one_off_transaction_result, recovery_operator_identity,
};
use diesel::{ExpressionMethods, QueryDsl, insert_into};
use diesel_async::RunQueryDsl;
@@ -790,13 +790,20 @@ async fn approve_one_off_transaction_stores_result() {
assert_eq!(outcome, VoteOutcome::Approved);
let mut conn = db.get().await.unwrap();
let count: i64 = proposal_result::table
.filter(proposal_result::proposal_id.eq(proposal_id))
.count()
.get_result(&mut conn)
let (r, s, y_parity): (Vec<u8>, Vec<u8>, i32) = proposal_one_off_transaction_result::table
.find(proposal_id)
.select((
proposal_one_off_transaction_result::r,
proposal_one_off_transaction_result::s,
proposal_one_off_transaction_result::y_parity,
))
.first(&mut conn)
.await
.unwrap();
assert_eq!(count, 1);
.expect("an approved transaction must leave its signature");
assert_eq!(r.len(), 32, "r must be a 32-byte scalar");
assert_eq!(s.len(), 32, "s must be a 32-byte scalar");
assert!(y_parity == 0 || y_parity == 1, "y_parity must be a bit");
}
#[tokio::test]