mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-03-09 21:53:34 +00:00
chore: simple attestation program
This commit is contained in:
parent
01f7d254c9
commit
75c0c31472
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -4601,6 +4601,7 @@ name = "program_deployment"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"hex",
|
||||
"nssa",
|
||||
"nssa_core",
|
||||
"tokio",
|
||||
|
||||
@ -11,3 +11,4 @@ wallet.workspace = true
|
||||
|
||||
tokio = { workspace = true, features = ["macros"] }
|
||||
clap.workspace = true
|
||||
hex.workspace = true
|
||||
|
||||
509
examples/program_deployment/methods/guest/src/bin/attestation.rs
Normal file
509
examples/program_deployment/methods/guest/src/bin/attestation.rs
Normal file
@ -0,0 +1,509 @@
|
||||
use nssa_core::{
|
||||
account::{Account, AccountId, AccountWithMetadata, Data},
|
||||
program::{
|
||||
AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs,
|
||||
},
|
||||
};
|
||||
|
||||
// Attestation registry program.
|
||||
//
|
||||
// Any account holder can create attestations about any subject, identified by
|
||||
// a 32-byte key, with an arbitrary-length value. Only the original creator can
|
||||
// update or revoke their attestation.
|
||||
//
|
||||
// Data layout (stored in the attestation account's `data` field):
|
||||
// Bytes 0..32 : creator (AccountId, 32 bytes)
|
||||
// Bytes 32..64 : subject (AccountId, 32 bytes)
|
||||
// Bytes 64..96 : key ([u8; 32], 32 bytes)
|
||||
// Byte 96 : revoked (bool, 0 = active, 1 = revoked)
|
||||
// Bytes 97.. : value (variable length)
|
||||
//
|
||||
// Instructions:
|
||||
// 0x00 Attest — payload: subject (32) || key (32) || value (var)
|
||||
// accounts: [creator (authorized), attestation_account]
|
||||
// 0x01 Revoke — payload: (none)
|
||||
// accounts: [creator (authorized), attestation_account]
|
||||
|
||||
const ATTESTATION_HEADER_SIZE: usize = 97; // 32 + 32 + 32 + 1
|
||||
|
||||
struct Attestation {
|
||||
creator: AccountId,
|
||||
subject: AccountId,
|
||||
key: [u8; 32],
|
||||
revoked: bool,
|
||||
value: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Attestation {
|
||||
fn into_data(self) -> Data {
|
||||
let mut bytes = Vec::<u8>::new();
|
||||
bytes.extend_from_slice(&self.creator.to_bytes());
|
||||
bytes.extend_from_slice(&self.subject.to_bytes());
|
||||
bytes.extend_from_slice(&self.key);
|
||||
bytes.push(self.revoked as u8);
|
||||
bytes.extend_from_slice(&self.value);
|
||||
|
||||
Data::try_from(bytes).expect("Attestation data must fit within the allowed limits")
|
||||
}
|
||||
|
||||
fn parse(data: &Data) -> Option<Self> {
|
||||
let data = Vec::<u8>::from(data.clone());
|
||||
|
||||
if data.len() < ATTESTATION_HEADER_SIZE {
|
||||
return None;
|
||||
}
|
||||
|
||||
let creator = AccountId::new(
|
||||
data[0..32]
|
||||
.try_into()
|
||||
.expect("Creator must be 32 bytes"),
|
||||
);
|
||||
let subject = AccountId::new(
|
||||
data[32..64]
|
||||
.try_into()
|
||||
.expect("Subject must be 32 bytes"),
|
||||
);
|
||||
let key: [u8; 32] = data[64..96]
|
||||
.try_into()
|
||||
.expect("Key must be 32 bytes");
|
||||
let revoked = data[96] != 0;
|
||||
let value = data[97..].to_vec();
|
||||
|
||||
Some(Self {
|
||||
creator,
|
||||
subject,
|
||||
key,
|
||||
revoked,
|
||||
value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn attest(pre_states: &[AccountWithMetadata], payload: &[u8]) -> Vec<AccountPostState> {
|
||||
if pre_states.len() != 2 {
|
||||
panic!("Attest requires exactly 2 accounts");
|
||||
}
|
||||
|
||||
let creator_account = &pre_states[0];
|
||||
let attestation_account = &pre_states[1];
|
||||
|
||||
if !creator_account.is_authorized {
|
||||
panic!("Missing required authorization for creator");
|
||||
}
|
||||
|
||||
if payload.len() < 64 {
|
||||
panic!("Attest payload must contain at least subject (32) and key (32)");
|
||||
}
|
||||
|
||||
let subject = AccountId::new(
|
||||
payload[0..32]
|
||||
.try_into()
|
||||
.expect("Subject must be 32 bytes"),
|
||||
);
|
||||
let key: [u8; 32] = payload[32..64]
|
||||
.try_into()
|
||||
.expect("Key must be 32 bytes");
|
||||
let value = payload[64..].to_vec();
|
||||
|
||||
let attestation_post = if attestation_account.account == Account::default() {
|
||||
// Creating a new attestation
|
||||
let attestation = Attestation {
|
||||
creator: creator_account.account_id,
|
||||
subject,
|
||||
key,
|
||||
revoked: false,
|
||||
value,
|
||||
};
|
||||
|
||||
let mut account = attestation_account.account.clone();
|
||||
account.data = attestation.into_data();
|
||||
AccountPostState::new_claimed(account)
|
||||
} else {
|
||||
// Updating an existing attestation
|
||||
let mut existing = Attestation::parse(&attestation_account.account.data)
|
||||
.expect("Invalid existing attestation data");
|
||||
|
||||
assert_eq!(
|
||||
existing.creator, creator_account.account_id,
|
||||
"Only the original creator can update an attestation"
|
||||
);
|
||||
assert!(
|
||||
!existing.revoked,
|
||||
"Cannot update a revoked attestation"
|
||||
);
|
||||
|
||||
existing.value = value;
|
||||
|
||||
let mut account = attestation_account.account.clone();
|
||||
account.data = existing.into_data();
|
||||
AccountPostState::new(account)
|
||||
};
|
||||
|
||||
let creator_post = AccountPostState::new(creator_account.account.clone());
|
||||
|
||||
vec![creator_post, attestation_post]
|
||||
}
|
||||
|
||||
fn revoke(pre_states: &[AccountWithMetadata]) -> Vec<AccountPostState> {
|
||||
if pre_states.len() != 2 {
|
||||
panic!("Revoke requires exactly 2 accounts");
|
||||
}
|
||||
|
||||
let creator_account = &pre_states[0];
|
||||
let attestation_account = &pre_states[1];
|
||||
|
||||
if !creator_account.is_authorized {
|
||||
panic!("Missing required authorization for creator");
|
||||
}
|
||||
|
||||
if attestation_account.account == Account::default() {
|
||||
panic!("Cannot revoke a non-existent attestation");
|
||||
}
|
||||
|
||||
let mut existing = Attestation::parse(&attestation_account.account.data)
|
||||
.expect("Invalid existing attestation data");
|
||||
|
||||
assert_eq!(
|
||||
existing.creator, creator_account.account_id,
|
||||
"Only the original creator can revoke an attestation"
|
||||
);
|
||||
assert!(
|
||||
!existing.revoked,
|
||||
"Attestation is already revoked"
|
||||
);
|
||||
|
||||
existing.revoked = true;
|
||||
|
||||
let mut account = attestation_account.account.clone();
|
||||
account.data = existing.into_data();
|
||||
|
||||
let creator_post = AccountPostState::new(creator_account.account.clone());
|
||||
let attestation_post = AccountPostState::new(account);
|
||||
|
||||
vec![creator_post, attestation_post]
|
||||
}
|
||||
|
||||
type Instruction = Vec<u8>;
|
||||
|
||||
fn main() {
|
||||
let (
|
||||
ProgramInput {
|
||||
pre_states,
|
||||
instruction,
|
||||
},
|
||||
instruction_data,
|
||||
) = read_nssa_inputs::<Instruction>();
|
||||
|
||||
let post_states = match instruction[0] {
|
||||
0 => attest(&pre_states, &instruction[1..]),
|
||||
1 => revoke(&pre_states),
|
||||
_ => panic!("Invalid instruction opcode"),
|
||||
};
|
||||
|
||||
write_nssa_outputs(instruction_data, pre_states, post_states);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use nssa_core::account::{Account, AccountId, AccountWithMetadata, Data};
|
||||
|
||||
use crate::{ATTESTATION_HEADER_SIZE, Attestation, attest, revoke};
|
||||
|
||||
fn creator_id() -> AccountId {
|
||||
AccountId::new([1; 32])
|
||||
}
|
||||
|
||||
fn subject_id() -> AccountId {
|
||||
AccountId::new([2; 32])
|
||||
}
|
||||
|
||||
fn attestation_key() -> [u8; 32] {
|
||||
[3; 32]
|
||||
}
|
||||
|
||||
fn creator_account(is_authorized: bool) -> AccountWithMetadata {
|
||||
AccountWithMetadata {
|
||||
account: Account {
|
||||
program_owner: [5u32; 8],
|
||||
balance: 0,
|
||||
data: Data::default(),
|
||||
nonce: 0,
|
||||
},
|
||||
is_authorized,
|
||||
account_id: creator_id(),
|
||||
}
|
||||
}
|
||||
|
||||
fn uninit_attestation_account() -> AccountWithMetadata {
|
||||
AccountWithMetadata {
|
||||
account: Account::default(),
|
||||
is_authorized: false,
|
||||
account_id: AccountId::new([10; 32]),
|
||||
}
|
||||
}
|
||||
|
||||
fn existing_attestation_account(revoked: bool, value: &[u8]) -> AccountWithMetadata {
|
||||
let attestation = Attestation {
|
||||
creator: creator_id(),
|
||||
subject: subject_id(),
|
||||
key: attestation_key(),
|
||||
revoked,
|
||||
value: value.to_vec(),
|
||||
};
|
||||
AccountWithMetadata {
|
||||
account: Account {
|
||||
program_owner: [5u32; 8],
|
||||
balance: 0,
|
||||
data: attestation.into_data(),
|
||||
nonce: 0,
|
||||
},
|
||||
is_authorized: false,
|
||||
account_id: AccountId::new([10; 32]),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_attest_payload(subject: &AccountId, key: &[u8; 32], value: &[u8]) -> Vec<u8> {
|
||||
let mut payload = Vec::new();
|
||||
payload.extend_from_slice(subject.value());
|
||||
payload.extend_from_slice(key);
|
||||
payload.extend_from_slice(value);
|
||||
payload
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_attestation_serialize_deserialize() {
|
||||
let original = Attestation {
|
||||
creator: creator_id(),
|
||||
subject: subject_id(),
|
||||
key: attestation_key(),
|
||||
revoked: false,
|
||||
value: b"hello world".to_vec(),
|
||||
};
|
||||
|
||||
let data = original.into_data();
|
||||
let parsed = Attestation::parse(&data).expect("Should parse successfully");
|
||||
|
||||
assert_eq!(parsed.creator, creator_id());
|
||||
assert_eq!(parsed.subject, subject_id());
|
||||
assert_eq!(parsed.key, attestation_key());
|
||||
assert!(!parsed.revoked);
|
||||
assert_eq!(parsed.value, b"hello world");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_attestation_parse_invalid_length() {
|
||||
let short_data = Data::try_from(vec![0u8; ATTESTATION_HEADER_SIZE - 1]).unwrap();
|
||||
assert!(Attestation::parse(&short_data).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_attestation_parse_empty() {
|
||||
let empty_data = Data::default();
|
||||
assert!(Attestation::parse(&empty_data).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_attest_create_new() {
|
||||
let pre_states = vec![creator_account(true), uninit_attestation_account()];
|
||||
let payload = build_attest_payload(&subject_id(), &attestation_key(), b"test value");
|
||||
|
||||
let post_states = attest(&pre_states, &payload);
|
||||
assert_eq!(post_states.len(), 2);
|
||||
|
||||
// Creator account should be unchanged
|
||||
assert_eq!(*post_states[0].account(), pre_states[0].account);
|
||||
assert!(!post_states[0].requires_claim());
|
||||
|
||||
// Attestation account should be claimed with new data
|
||||
assert!(post_states[1].requires_claim());
|
||||
let parsed = Attestation::parse(&post_states[1].account().data)
|
||||
.expect("Should parse attestation");
|
||||
assert_eq!(parsed.creator, creator_id());
|
||||
assert_eq!(parsed.subject, subject_id());
|
||||
assert_eq!(parsed.key, attestation_key());
|
||||
assert!(!parsed.revoked);
|
||||
assert_eq!(parsed.value, b"test value");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_attest_update_existing() {
|
||||
let pre_states = vec![
|
||||
creator_account(true),
|
||||
existing_attestation_account(false, b"old value"),
|
||||
];
|
||||
let payload = build_attest_payload(&subject_id(), &attestation_key(), b"new value");
|
||||
|
||||
let post_states = attest(&pre_states, &payload);
|
||||
assert_eq!(post_states.len(), 2);
|
||||
|
||||
assert!(!post_states[1].requires_claim());
|
||||
let parsed = Attestation::parse(&post_states[1].account().data)
|
||||
.expect("Should parse attestation");
|
||||
assert_eq!(parsed.value, b"new value");
|
||||
assert!(!parsed.revoked);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Missing required authorization for creator")]
|
||||
fn test_attest_missing_authorization() {
|
||||
let pre_states = vec![creator_account(false), uninit_attestation_account()];
|
||||
let payload = build_attest_payload(&subject_id(), &attestation_key(), b"test");
|
||||
|
||||
attest(&pre_states, &payload);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Only the original creator can update an attestation")]
|
||||
fn test_attest_update_wrong_creator() {
|
||||
let different_creator = AccountWithMetadata {
|
||||
account: Account {
|
||||
program_owner: [5u32; 8],
|
||||
balance: 0,
|
||||
data: Data::default(),
|
||||
nonce: 0,
|
||||
},
|
||||
is_authorized: true,
|
||||
account_id: AccountId::new([99; 32]),
|
||||
};
|
||||
let pre_states = vec![
|
||||
different_creator,
|
||||
existing_attestation_account(false, b"old"),
|
||||
];
|
||||
let payload = build_attest_payload(&subject_id(), &attestation_key(), b"new");
|
||||
|
||||
attest(&pre_states, &payload);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Cannot update a revoked attestation")]
|
||||
fn test_attest_update_revoked() {
|
||||
let pre_states = vec![
|
||||
creator_account(true),
|
||||
existing_attestation_account(true, b"old"),
|
||||
];
|
||||
let payload = build_attest_payload(&subject_id(), &attestation_key(), b"new");
|
||||
|
||||
attest(&pre_states, &payload);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Attest requires exactly 2 accounts")]
|
||||
fn test_attest_wrong_account_count() {
|
||||
let pre_states = vec![creator_account(true)];
|
||||
let payload = build_attest_payload(&subject_id(), &attestation_key(), b"test");
|
||||
|
||||
attest(&pre_states, &payload);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Attest payload must contain at least subject (32) and key (32)")]
|
||||
fn test_attest_payload_too_short() {
|
||||
let pre_states = vec![creator_account(true), uninit_attestation_account()];
|
||||
|
||||
attest(&pre_states, &[0u8; 63]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_revoke_success() {
|
||||
let pre_states = vec![
|
||||
creator_account(true),
|
||||
existing_attestation_account(false, b"some value"),
|
||||
];
|
||||
|
||||
let post_states = revoke(&pre_states);
|
||||
assert_eq!(post_states.len(), 2);
|
||||
|
||||
assert_eq!(*post_states[0].account(), pre_states[0].account);
|
||||
assert!(!post_states[0].requires_claim());
|
||||
|
||||
assert!(!post_states[1].requires_claim());
|
||||
let parsed = Attestation::parse(&post_states[1].account().data)
|
||||
.expect("Should parse attestation");
|
||||
assert!(parsed.revoked);
|
||||
assert_eq!(parsed.value, b"some value");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Missing required authorization for creator")]
|
||||
fn test_revoke_missing_authorization() {
|
||||
let pre_states = vec![
|
||||
creator_account(false),
|
||||
existing_attestation_account(false, b"val"),
|
||||
];
|
||||
|
||||
revoke(&pre_states);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Cannot revoke a non-existent attestation")]
|
||||
fn test_revoke_nonexistent() {
|
||||
let pre_states = vec![creator_account(true), uninit_attestation_account()];
|
||||
|
||||
revoke(&pre_states);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Only the original creator can revoke an attestation")]
|
||||
fn test_revoke_wrong_creator() {
|
||||
let different_creator = AccountWithMetadata {
|
||||
account: Account {
|
||||
program_owner: [5u32; 8],
|
||||
balance: 0,
|
||||
data: Data::default(),
|
||||
nonce: 0,
|
||||
},
|
||||
is_authorized: true,
|
||||
account_id: AccountId::new([99; 32]),
|
||||
};
|
||||
let pre_states = vec![
|
||||
different_creator,
|
||||
existing_attestation_account(false, b"val"),
|
||||
];
|
||||
|
||||
revoke(&pre_states);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Attestation is already revoked")]
|
||||
fn test_revoke_already_revoked() {
|
||||
let pre_states = vec![
|
||||
creator_account(true),
|
||||
existing_attestation_account(true, b"val"),
|
||||
];
|
||||
|
||||
revoke(&pre_states);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Revoke requires exactly 2 accounts")]
|
||||
fn test_revoke_wrong_account_count() {
|
||||
let pre_states = vec![creator_account(true)];
|
||||
|
||||
revoke(&pre_states);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_attest_empty_value() {
|
||||
let pre_states = vec![creator_account(true), uninit_attestation_account()];
|
||||
let payload = build_attest_payload(&subject_id(), &attestation_key(), b"");
|
||||
|
||||
let post_states = attest(&pre_states, &payload);
|
||||
|
||||
let parsed = Attestation::parse(&post_states[1].account().data)
|
||||
.expect("Should parse attestation");
|
||||
assert_eq!(parsed.value, b"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_attestation_header_size_matches_parse() {
|
||||
let attestation = Attestation {
|
||||
creator: creator_id(),
|
||||
subject: subject_id(),
|
||||
key: attestation_key(),
|
||||
revoked: false,
|
||||
value: vec![],
|
||||
};
|
||||
let data = attestation.into_data();
|
||||
assert_eq!(data.len(), ATTESTATION_HEADER_SIZE);
|
||||
}
|
||||
}
|
||||
161
examples/program_deployment/src/bin/run_attestation.rs
Normal file
161
examples/program_deployment/src/bin/run_attestation.rs
Normal file
@ -0,0 +1,161 @@
|
||||
use clap::{Parser, Subcommand};
|
||||
use nssa::{
|
||||
AccountId, PublicTransaction,
|
||||
program::Program,
|
||||
public_transaction::{Message, WitnessSet},
|
||||
};
|
||||
use wallet::WalletCore;
|
||||
|
||||
// Before running this example, compile the `attestation.rs` guest program with:
|
||||
//
|
||||
// cargo risczero build --manifest-path examples/program_deployment/methods/guest/Cargo.toml
|
||||
//
|
||||
// Note: you must run the above command from the root of the repository.
|
||||
// Note: The compiled binary file is stored in
|
||||
// methods/guest/target/riscv32im-risc0-zkvm-elf/docker/attestation.bin
|
||||
//
|
||||
// Usage:
|
||||
// cargo run --bin run_attestation -- <program_binary_path> <subcommand>
|
||||
//
|
||||
// Examples:
|
||||
// cargo run --bin run_attestation -- \
|
||||
// methods/guest/target/riscv32im-risc0-zkvm-elf/docker/attestation.bin \
|
||||
// attest <creator_id> <attestation_id> <subject_id> <key_hex> <value_string>
|
||||
//
|
||||
// cargo run --bin run_attestation -- \
|
||||
// methods/guest/target/riscv32im-risc0-zkvm-elf/docker/attestation.bin \
|
||||
// revoke <creator_id> <attestation_id>
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct Cli {
|
||||
/// Path to the attestation program binary
|
||||
program_path: String,
|
||||
|
||||
#[command(subcommand)]
|
||||
command: Command,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum Command {
|
||||
/// Create or update an attestation
|
||||
Attest {
|
||||
/// Account ID of the creator (must be a self-owned public account)
|
||||
creator_id: String,
|
||||
/// Account ID for the attestation record
|
||||
attestation_id: String,
|
||||
/// Account ID of the subject being attested about
|
||||
subject_id: String,
|
||||
/// 32-byte attestation key as a hex string (64 hex chars)
|
||||
key_hex: String,
|
||||
/// Attestation value as a UTF-8 string
|
||||
value_string: String,
|
||||
},
|
||||
/// Revoke an existing attestation
|
||||
Revoke {
|
||||
/// Account ID of the creator (must be a self-owned public account)
|
||||
creator_id: String,
|
||||
/// Account ID of the attestation record to revoke
|
||||
attestation_id: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let cli = Cli::parse();
|
||||
|
||||
// Load the program
|
||||
let bytecode: Vec<u8> = std::fs::read(cli.program_path).unwrap();
|
||||
let program = Program::new(bytecode).unwrap();
|
||||
|
||||
// Initialize wallet
|
||||
let wallet_core = WalletCore::from_env().unwrap();
|
||||
|
||||
match cli.command {
|
||||
Command::Attest {
|
||||
creator_id,
|
||||
attestation_id,
|
||||
subject_id,
|
||||
key_hex,
|
||||
value_string,
|
||||
} => {
|
||||
let creator_id: AccountId = creator_id.parse().unwrap();
|
||||
let attestation_id: AccountId = attestation_id.parse().unwrap();
|
||||
let subject_id: AccountId = subject_id.parse().unwrap();
|
||||
let key_bytes: [u8; 32] = hex::decode(&key_hex)
|
||||
.expect("key_hex must be valid hex")
|
||||
.try_into()
|
||||
.expect("key_hex must decode to exactly 32 bytes");
|
||||
|
||||
let signing_key = wallet_core
|
||||
.storage()
|
||||
.user_data
|
||||
.get_pub_account_signing_key(&creator_id)
|
||||
.expect("Creator account should be a self-owned public account");
|
||||
|
||||
let nonces = wallet_core
|
||||
.get_accounts_nonces(vec![creator_id])
|
||||
.await
|
||||
.expect("Node should be reachable to query account data");
|
||||
|
||||
// Build instruction: [0x00 || subject (32) || key (32) || value (var)]
|
||||
let mut instruction: Vec<u8> = Vec::new();
|
||||
instruction.push(0x00);
|
||||
instruction.extend_from_slice(subject_id.value());
|
||||
instruction.extend_from_slice(&key_bytes);
|
||||
instruction.extend_from_slice(value_string.as_bytes());
|
||||
|
||||
let message = Message::try_new(
|
||||
program.id(),
|
||||
vec![creator_id, attestation_id],
|
||||
nonces,
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
let witness_set = WitnessSet::for_message(&message, &[signing_key]);
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
|
||||
let _response = wallet_core
|
||||
.sequencer_client
|
||||
.send_tx_public(tx)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
Command::Revoke {
|
||||
creator_id,
|
||||
attestation_id,
|
||||
} => {
|
||||
let creator_id: AccountId = creator_id.parse().unwrap();
|
||||
let attestation_id: AccountId = attestation_id.parse().unwrap();
|
||||
|
||||
let signing_key = wallet_core
|
||||
.storage()
|
||||
.user_data
|
||||
.get_pub_account_signing_key(&creator_id)
|
||||
.expect("Creator account should be a self-owned public account");
|
||||
|
||||
let nonces = wallet_core
|
||||
.get_accounts_nonces(vec![creator_id])
|
||||
.await
|
||||
.expect("Node should be reachable to query account data");
|
||||
|
||||
// Build instruction: [0x01]
|
||||
let instruction: Vec<u8> = vec![0x01];
|
||||
|
||||
let message = Message::try_new(
|
||||
program.id(),
|
||||
vec![creator_id, attestation_id],
|
||||
nonces,
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
let witness_set = WitnessSet::for_message(&message, &[signing_key]);
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
|
||||
let _response = wallet_core
|
||||
.sequencer_client
|
||||
.send_tx_public(tx)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user