More tests for council voting.

Also allow AsRef to be used for Public keys to simplify test code.
This commit is contained in:
Gav
2018-03-05 12:11:56 +01:00
parent 8d84ca8b48
commit 7d378d3de3
11 changed files with 273 additions and 66 deletions
+1
View File
@@ -1598,6 +1598,7 @@ version = "0.1.0"
dependencies = [
"ed25519 0.1.0",
"hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
+16 -6
View File
@@ -44,6 +44,8 @@ enum InternalFunctionId {
StakingSetValidatorCount = 0x22,
/// Force a new staking era.
StakingForceNewEra = 0x23,
/// See below.
DemocracyCancelReferendum = 0x30,
}
impl InternalFunctionId {
@@ -57,6 +59,7 @@ impl InternalFunctionId {
InternalFunctionId::StakingSetBondingDuration,
InternalFunctionId::StakingSetValidatorCount,
InternalFunctionId::StakingForceNewEra,
InternalFunctionId::DemocracyCancelReferendum,
];
functions.iter().map(|&f| f).find(|&f| value == f as u8)
}
@@ -80,24 +83,27 @@ pub enum Proposal {
StakingSetValidatorCount(u32),
/// Force a new staking era.
StakingForceNewEra,
/// Cancel a referendum.
DemocracyCancelReferendum(u32),
}
impl Slicable for Proposal {
fn decode<I: Input>(input: &mut I) -> Option<Self> {
let id = try_opt!(u8::decode(input).and_then(InternalFunctionId::from_u8));
let id = u8::decode(input).and_then(InternalFunctionId::from_u8)?;
let function = match id {
InternalFunctionId::SystemSetCode =>
Proposal::SystemSetCode(try_opt!(Slicable::decode(input))),
Proposal::SystemSetCode(Slicable::decode(input)?),
InternalFunctionId::SessionSetLength =>
Proposal::SessionSetLength(try_opt!(Slicable::decode(input))),
Proposal::SessionSetLength(Slicable::decode(input)?),
InternalFunctionId::SessionForceNewSession => Proposal::SessionForceNewSession,
InternalFunctionId::StakingSetSessionsPerEra =>
Proposal::StakingSetSessionsPerEra(try_opt!(Slicable::decode(input))),
Proposal::StakingSetSessionsPerEra(Slicable::decode(input)?),
InternalFunctionId::StakingSetBondingDuration =>
Proposal::StakingSetBondingDuration(try_opt!(Slicable::decode(input))),
Proposal::StakingSetBondingDuration(Slicable::decode(input)?),
InternalFunctionId::StakingSetValidatorCount =>
Proposal::StakingSetValidatorCount(try_opt!(Slicable::decode(input))),
Proposal::StakingSetValidatorCount(Slicable::decode(input)?),
InternalFunctionId::StakingForceNewEra => Proposal::StakingForceNewEra,
InternalFunctionId::DemocracyCancelReferendum => Proposal::DemocracyCancelReferendum(Slicable::decode(input)?),
};
Some(function)
@@ -132,6 +138,10 @@ impl Slicable for Proposal {
Proposal::StakingForceNewEra => {
(InternalFunctionId::StakingForceNewEra as u8).using_encoded(|s| v.extend(s));
}
Proposal::DemocracyCancelReferendum(ref data) => {
(InternalFunctionId::DemocracyCancelReferendum as u8).using_encoded(|s| v.extend(s));
data.using_encoded(|s| v.extend(s));
}
}
v
+4 -1
View File
@@ -17,7 +17,7 @@
//! Democratic system: Handles administration of general stakeholder voting.
use demo_primitives::Proposal;
use runtime::{staking, system, session};
use runtime::{staking, system, session, democracy};
pub fn enact_proposal(proposal: Proposal) {
match proposal {
@@ -42,5 +42,8 @@ pub fn enact_proposal(proposal: Proposal) {
Proposal::StakingForceNewEra => {
staking::privileged::force_new_era()
}
Proposal::DemocracyCancelReferendum(ref_index) => {
democracy::privileged::clear_referendum(ref_index)
}
}
}
@@ -91,8 +91,8 @@ pub const TERM_DURATION: &[u8] = b"cou:trm";
pub const DESIRED_SEATS: &[u8] = b"cou:sts";
// permanent state (always relevant, changes only at the finalisation of voting)
pub const ACTIVE_COUNCIL: &[u8] = b"cou:act";
pub const VOTE_COUNT: &[u8] = b"cou:vco";
pub const ACTIVE_COUNCIL: &[u8] = b"cou:act"; // Vec<(AccountId, expiry: BlockNumber)>
pub const VOTE_COUNT: &[u8] = b"cou:vco"; // VoteIndex
// persistent state (always relevant, changes constantly)
pub const APPROVALS_OF: &[u8] = b"cou:apr:"; // Vec<bool>
@@ -51,27 +51,27 @@ pub fn was_vetoed(proposal: &ProposalHash) -> bool {
storage::exists(&proposal.to_keyed_vec(VETOED_PROPOSAL))
}
pub fn will_still_be_councillor_at(who: &AccountId, n: BlockNumber) -> bool {
pub fn will_still_be_councillor_at<P: AsRef<AccountId>>(who: P, n: BlockNumber) -> bool {
council::active_council().iter()
.find(|&&(ref a, _)| a == who)
.find(|&&(ref a, _)| a == who.as_ref())
.map(|&(_, expires)| expires > n)
.unwrap_or(false)
}
pub fn vote_of(who: &AccountId, proposal: &ProposalHash) -> Option<bool> {
storage::get(&(*who, *proposal).to_keyed_vec(COUNCIL_VOTE_OF))
pub fn vote_of<P: AsRef<AccountId>>(who: P, proposal: &ProposalHash) -> Option<bool> {
storage::get(&(*proposal, *who.as_ref()).to_keyed_vec(COUNCIL_VOTE_OF))
}
pub fn take_vote_of(who: &AccountId, proposal: &ProposalHash) -> Option<bool> {
storage::get(&(*who, *proposal).to_keyed_vec(COUNCIL_VOTE_OF))
pub fn proposal_voters(proposal: &ProposalHash) -> Vec<AccountId> {
storage::get_or_default(&proposal.to_keyed_vec(PROPOSAL_VOTERS))
}
pub fn tally(proposal_hash: &ProposalHash) -> (u32, u32, u32) {
generic_tally(proposal_hash, vote_of)
generic_tally(proposal_hash, |w: &AccountId, p: &ProposalHash| storage::get(&(*p, *w).to_keyed_vec(COUNCIL_VOTE_OF)))
}
fn take_tally(proposal_hash: &ProposalHash) -> (u32, u32, u32) {
generic_tally(proposal_hash, take_vote_of)
generic_tally(proposal_hash, |w: &AccountId, p: &ProposalHash| storage::get(&(*p, *w).to_keyed_vec(COUNCIL_VOTE_OF)))
}
fn generic_tally<F: Fn(&AccountId, &ProposalHash) -> Option<bool>>(proposal_hash: &ProposalHash, vote_of: F) -> (u32, u32, u32) {
@@ -103,7 +103,7 @@ fn take_proposal_if_expiring_at(n: BlockNumber) -> Option<(Proposal, ProposalHas
pub mod public {
use super::*;
pub fn propose(signed: &AccountId, proposal: &Proposal) {
pub fn propose<P: AsRef<AccountId> + Copy>(signed: P, proposal: &Proposal) {
let expiry = system::block_number() + voting_period();
assert!(will_still_be_councillor_at(signed, expiry));
@@ -116,19 +116,24 @@ pub mod public {
set_proposals(&proposals);
storage::put(&proposal_hash.to_keyed_vec(PROPOSAL_OF), proposal);
storage::put(&proposal_hash.to_keyed_vec(PROPOSAL_VOTERS), &vec![*signed]);
storage::put(&(proposal_hash, *signed).to_keyed_vec(COUNCIL_VOTE_OF), &true);
storage::put(&proposal_hash.to_keyed_vec(PROPOSAL_VOTERS), &vec![*signed.as_ref()]);
storage::put(&(proposal_hash, *(signed.as_ref())).to_keyed_vec(COUNCIL_VOTE_OF), &true);
}
pub fn vote(signed: AccountId, proposal: &ProposalHash, approve: bool) {
pub fn vote<P: AsRef<AccountId> + Copy>(signed: P, proposal: &ProposalHash, approve: bool) {
if vote_of(signed, proposal).is_none() {
let mut voters = proposal_voters(proposal);
voters.push(*signed.as_ref());
storage::put(&proposal.to_keyed_vec(PROPOSAL_VOTERS), &voters);
}
storage::put(&(*proposal, *(signed.as_ref())).to_keyed_vec(COUNCIL_VOTE_OF), &approve);
}
pub fn veto<P: AsRef<AccountId> + Copy>(signed: P, proposal: &ProposalHash) {
}
pub fn veto(signed: AccountId, proposal: &ProposalHash) {
}
pub fn repropose(signed: AccountId, proposal: &Proposal) {
pub fn repropose<P: AsRef<AccountId> + Copy>(signed: P, proposal: &Proposal) {
}
}
@@ -153,17 +158,147 @@ pub mod internal {
pub fn end_block(now: BlockNumber) {
while let Some((proposal, proposal_hash)) = take_proposal_if_expiring_at(now) {
let tally = take_tally(&proposal_hash);
let vote_threshold = match tally.0 {
x if x == tally.2 => VoteThreshold::SuperMajorityAgainst,
x if x > tally.2 / 2 => VoteThreshold::SimpleMajority,
_ => VoteThreshold::SuperMajorityApprove,
if let Proposal::DemocracyCancelReferendum(ref_index) = proposal {
if tally.0 == tally.2 {
democracy::privileged::clear_referendum(ref_index);
}
} else {
match tally {
(_, 0, 0) => start_referendum(proposal, VoteThreshold::SuperMajorityAgainst),
(y, n, x) if y > n + x => start_referendum(proposal, VoteThreshold::SimpleMajority),
_ => {},
};
start_referendum(proposal, vote_threshold);
}
}
}
}
#[cfg(test)]
mod tests {
pub mod testing {
use super::*;
use runtime_io::{twox_128, TestExternalities};
use keyring::Keyring::{Alice, Bob, Charlie};
use codec::Joiner;
use runtime::council;
pub fn externalities() -> TestExternalities {
let expiry: BlockNumber = 10;
let extras: TestExternalities = map![
twox_128(council::ACTIVE_COUNCIL).to_vec() => vec![].and(&vec![
(Alice.to_raw_public(), expiry),
(Bob.into(), expiry),
(Charlie.into(), expiry)
]),
twox_128(COOLOFF_PERIOD).to_vec() => vec![].and(&2u64),
twox_128(VOTING_PERIOD).to_vec() => vec![].and(&1u64)
];
council::testing::externalities()
.into_iter().chain(extras.into_iter()).collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use runtime_io::{with_externalities, twox_128, TestExternalities};
use codec::{KeyedVec, Joiner};
use keyring::Keyring::{Alice, Bob, Charlie, Dave};
use environment::with_env;
use demo_primitives::{AccountId, Proposal};
use runtime::{staking, council, democracy};
use runtime::democracy::VoteThreshold;
fn new_test_ext() -> TestExternalities {
testing::externalities()
}
#[test]
fn basic_environment_works() {
with_externalities(&mut new_test_ext(), || {
with_env(|e| e.block_number = 1);
assert_eq!(staking::bonding_duration(), 0);
assert_eq!(cooloff_period(), 2);
assert_eq!(voting_period(), 1);
assert_eq!(will_still_be_councillor_at(Alice, 1), true);
assert_eq!(will_still_be_councillor_at(Alice, 10), false);
assert_eq!(will_still_be_councillor_at(Dave, 10), false);
assert_eq!(proposals(), Vec::<(BlockNumber, ProposalHash)>::new());
assert_eq!(proposal_voters(&ProposalHash::default()), Vec::<AccountId>::new());
assert_eq!(was_vetoed(&ProposalHash::default()), false);
assert_eq!(vote_of(Alice, &ProposalHash::default()), None);
assert_eq!(tally(&ProposalHash::default()), (0, 0, 3));
});
}
#[test]
fn simple_propose_should_work() {
with_externalities(&mut new_test_ext(), || {
with_env(|e| e.block_number = 1);
let proposal = Proposal::StakingSetBondingDuration(42);
let hash = proposal.blake2_256();
public::propose(Alice, &proposal);
assert_eq!(proposals().len(), 1);
assert_eq!(proposal_voters(&hash), vec![Alice.to_raw_public()]);
assert_eq!(vote_of(Alice, &hash), Some(true));
assert_eq!(tally(&hash), (1, 0, 2));
});
}
#[test]
fn unvoted_proposal_should_expire_without_action() {
with_externalities(&mut new_test_ext(), || {
with_env(|e| e.block_number = 1);
public::propose(Alice, &Proposal::StakingSetBondingDuration(42));
assert_eq!(tally(&Proposal::StakingSetBondingDuration(42).blake2_256()), (1, 0, 2));
internal::end_block(1);
with_env(|e| e.block_number = 2);
internal::end_block(2);
assert_eq!(proposals().len(), 0);
assert_eq!(democracy::active_referendums().len(), 0);
});
}
#[test]
fn unanimous_proposal_should_expire_with_biased_referendum() {
with_externalities(&mut new_test_ext(), || {
with_env(|e| e.block_number = 1);
public::propose(Alice, &Proposal::StakingSetBondingDuration(42));
public::vote(Bob, &Proposal::StakingSetBondingDuration(42).blake2_256(), true);
public::vote(Charlie, &Proposal::StakingSetBondingDuration(42).blake2_256(), true);
assert_eq!(tally(&Proposal::StakingSetBondingDuration(42).blake2_256()), (3, 0, 0));
internal::end_block(1);
with_env(|e| e.block_number = 2);
internal::end_block(2);
assert_eq!(proposals().len(), 0);
assert_eq!(democracy::active_referendums(), vec![(0, 3, Proposal::StakingSetBondingDuration(42), VoteThreshold::SuperMajorityAgainst)]);
});
}
#[test]
fn majority_proposal_should_expire_with_unbiased_referendum() {
with_externalities(&mut new_test_ext(), || {
with_env(|e| e.block_number = 1);
public::propose(Alice, &Proposal::StakingSetBondingDuration(42));
public::vote(Bob, &Proposal::StakingSetBondingDuration(42).blake2_256(), true);
public::vote(Charlie, &Proposal::StakingSetBondingDuration(42).blake2_256(), false);
assert_eq!(tally(&Proposal::StakingSetBondingDuration(42).blake2_256()), (2, 1, 0));
internal::end_block(1);
with_env(|e| e.block_number = 2);
internal::end_block(2);
assert_eq!(proposals().len(), 0);
assert_eq!(democracy::active_referendums(), vec![(0, 3, Proposal::StakingSetBondingDuration(42), VoteThreshold::SimpleMajority)]);
});
}
#[test]
#[should_panic]
fn propose_by_public_should_panic() {
with_externalities(&mut new_test_ext(), || {
with_env(|e| e.block_number = 1);
public::propose(Dave, &Proposal::StakingSetBondingDuration(42));
});
}
}
@@ -27,6 +27,8 @@ use runtime::staking::Balance;
pub type PropIndex = u32;
pub type ReferendumIndex = u32;
#[cfg_attr(test, derive(Debug))]
#[derive(Clone, Copy, PartialEq)]
pub enum VoteThreshold {
SuperMajorityApprove,
SuperMajorityAgainst,
@@ -138,6 +140,15 @@ pub fn referendum_info(ref_index: ReferendumIndex) -> Option<(BlockNumber, Propo
storage::get(&ref_index.to_keyed_vec(REFERENDUM_INFO_OF))
}
/// Get all referendums currently active.
pub fn active_referendums() -> Vec<(ReferendumIndex, BlockNumber, Proposal, VoteThreshold)> {
let next: ReferendumIndex = storage::get_or_default(NEXT_TALLY);
let last: ReferendumIndex = storage::get_or_default(REFERENDUM_COUNT);
(next..last).into_iter()
.filter_map(|i| referendum_info(i).map(|(n, p, t)| (i, n, p, t)))
.collect()
}
/// Get all referendums ready for tally at block `n`.
pub fn maturing_referendums_at(n: BlockNumber) -> Vec<(ReferendumIndex, BlockNumber, Proposal, VoteThreshold)> {
let next: ReferendumIndex = storage::get_or_default(NEXT_TALLY);
+20 -8
View File
@@ -37,8 +37,8 @@ pub struct LocalizedSignature {
}
/// Verify a message without type checking the parameters' types for the right size.
pub fn verify(sig: &[u8], message: &[u8], public: &[u8]) -> bool {
let public_key = untrusted::Input::from(public);
pub fn verify<P: AsRef<[u8]>>(sig: &[u8], message: &[u8], public: P) -> bool {
let public_key = untrusted::Input::from(public.as_ref());
let msg = untrusted::Input::from(message);
let sig = untrusted::Input::from(sig);
@@ -104,6 +104,18 @@ impl Into<[u8; 32]> for Public {
}
}
impl AsRef<Public> for Public {
fn as_ref(&self) -> &Public {
&self
}
}
impl AsRef<Pair> for Pair {
fn as_ref(&self) -> &Pair {
&self
}
}
impl Pair {
/// Generate new secure (random) key pair.
pub fn new() -> Pair {
@@ -144,8 +156,8 @@ impl Pair {
}
/// Verify a signature on a message.
pub fn verify_strong(sig: &Signature, message: &[u8], pubkey: &Public) -> bool {
let public_key = untrusted::Input::from(&pubkey.0[..]);
pub fn verify_strong<P: AsRef<Public>>(sig: &Signature, message: &[u8], pubkey: P) -> bool {
let public_key = untrusted::Input::from(&pubkey.as_ref().0[..]);
let msg = untrusted::Input::from(message);
let sig = untrusted::Input::from(&sig.0[..]);
@@ -157,19 +169,19 @@ pub fn verify_strong(sig: &Signature, message: &[u8], pubkey: &Public) -> bool {
pub trait Verifiable {
/// Verify something that acts like a signature.
fn verify(&self, message: &[u8], pubkey: &Public) -> bool;
fn verify<P: AsRef<Public>>(&self, message: &[u8], pubkey: P) -> bool;
}
impl Verifiable for Signature {
/// Verify something that acts like a signature.
fn verify(&self, message: &[u8], pubkey: &Public) -> bool {
fn verify<P: AsRef<Public>>(&self, message: &[u8], pubkey: P) -> bool {
verify_strong(&self, message, pubkey)
}
}
impl Verifiable for LocalizedSignature {
fn verify(&self, message: &[u8], pubkey: &Public) -> bool {
pubkey == &self.signer && self.signature.verify(message, pubkey)
fn verify<P: AsRef<Public>>(&self, message: &[u8], pubkey: P) -> bool {
pubkey.as_ref() == &self.signer && self.signature.verify(message, pubkey)
}
}
+1
View File
@@ -6,3 +6,4 @@ authors = ["Parity Technologies <admin@parity.io>"]
[dependencies]
ed25519 = { path = "../ed25519" }
hex-literal = { version = "0.1.0" }
lazy_static = { version = "1.0" }
+56 -22
View File
@@ -17,12 +17,14 @@
//! Support code for the runtime.
#[macro_use] extern crate hex_literal;
#[macro_use] extern crate lazy_static;
extern crate ed25519;
use std::collections::HashMap;
use ed25519::{Pair, Public, Signature};
/// Set of test accounts.
#[derive(Clone, Copy, PartialEq)]
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub enum Keyring {
Alice,
Bob,
@@ -63,7 +65,7 @@ impl Keyring {
}
pub fn sign(self, msg: &[u8]) -> Signature {
Pair::from(self).sign(msg)
AsRef::<Pair>::as_ref(&self).sign(msg)
}
}
@@ -82,32 +84,64 @@ impl From<Keyring> for &'static str {
}
}
impl From<Keyring> for Pair {
fn from(k: Keyring) -> Self {
match k {
Keyring::Alice => Pair::from_seed(b"Alice "),
Keyring::Bob => Pair::from_seed(b"Bob "),
Keyring::Charlie => Pair::from_seed(b"Charlie "),
Keyring::Dave => Pair::from_seed(b"Dave "),
Keyring::Eve => Pair::from_seed(b"Eve "),
Keyring::Ferdie => Pair::from_seed(b"Ferdie "),
Keyring::One => Pair::from_seed(b"12345678901234567890123456789012"),
Keyring::Two => Pair::from_seed(&hex!("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60")),
}
}
lazy_static! {
static ref PRIVATE_KEYS: HashMap<Keyring, Pair> = {
let mut m = HashMap::new();
m.insert(Keyring::Alice, Pair::from_seed(b"Alice "));
m.insert(Keyring::Bob, Pair::from_seed(b"Bob "));
m.insert(Keyring::Charlie, Pair::from_seed(b"Charlie "));
m.insert(Keyring::Dave, Pair::from_seed(b"Dave "));
m.insert(Keyring::Eve, Pair::from_seed(b"Eve "));
m.insert(Keyring::Ferdie, Pair::from_seed(b"Ferdie "));
m.insert(Keyring::One, Pair::from_seed(b"12345678901234567890123456789012"));
m.insert(Keyring::Two, Pair::from_seed(&hex!("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60")));
m
};
static ref PUBLIC_KEYS: HashMap<Keyring, Public> = {
PRIVATE_KEYS.iter().map(|(&name, pair)| (name, pair.public())).collect()
};
}
impl From<Keyring> for Public {
fn from(k: Keyring) -> Self {
let pair: Pair = k.into();
pair.public()
(*PUBLIC_KEYS).get(&k).unwrap().clone()
}
}
impl From<Keyring> for [u8; 32] {
fn from(k: Keyring) -> Self {
let pair: Pair = k.into();
*pair.public().as_array_ref()
*(*PUBLIC_KEYS).get(&k).unwrap().as_array_ref()
}
}
impl From<Keyring> for &'static [u8; 32] {
fn from(k: Keyring) -> Self {
(*PUBLIC_KEYS).get(&k).unwrap().as_array_ref()
}
}
impl AsRef<[u8; 32]> for Keyring {
fn as_ref(&self) -> &[u8; 32] {
(*PUBLIC_KEYS).get(self).unwrap().as_array_ref()
}
}
impl AsRef<[u8]> for Keyring {
fn as_ref(&self) -> &[u8] {
(*PUBLIC_KEYS).get(self).unwrap().as_array_ref()
}
}
impl AsRef<Public> for Keyring {
fn as_ref(&self) -> &Public {
(*PUBLIC_KEYS).get(self).unwrap()
}
}
impl AsRef<Pair> for Keyring {
fn as_ref(&self) -> &Pair {
(*PRIVATE_KEYS).get(self).unwrap()
}
}
@@ -118,8 +152,8 @@ mod tests {
#[test]
fn should_work() {
assert!(Keyring::Alice.sign(b"I am Alice!").verify(b"I am Alice!", &Keyring::Alice.into()));
assert!(!Keyring::Alice.sign(b"I am Alice!").verify(b"I am Bob!", &Keyring::Alice.into()));
assert!(!Keyring::Alice.sign(b"I am Alice!").verify(b"I am Alice!", &Keyring::Bob.into()));
assert!(Keyring::Alice.sign(b"I am Alice!").verify(b"I am Alice!", Keyring::Alice));
assert!(!Keyring::Alice.sign(b"I am Alice!").verify(b"I am Bob!", Keyring::Alice));
assert!(!Keyring::Alice.sign(b"I am Alice!").verify(b"I am Alice!", Keyring::Bob));
}
}
+1 -1
View File
@@ -86,7 +86,7 @@ pub fn enumerated_trie_root(serialised_values: &[&[u8]]) -> [u8; 32] {
}
/// Verify a ed25519 signature.
pub fn ed25519_verify(sig: &[u8; 64], msg: &[u8], pubkey: &[u8; 32]) -> bool {
pub fn ed25519_verify<P: AsRef<[u8]>>(sig: &[u8; 64], msg: &[u8], pubkey: P) -> bool {
ed25519::verify(sig, msg, pubkey)
}
@@ -161,9 +161,9 @@ pub fn twox_128(data: &[u8]) -> [u8; 16] {
}
/// Verify a ed25519 signature.
pub fn ed25519_verify(sig: &[u8; 64], msg: &[u8], pubkey: &[u8; 32]) -> bool {
pub fn ed25519_verify<P: AsRef<[u8]>>(sig: &[u8; 64], msg: &[u8], pubkey: P) -> bool {
unsafe {
ext_ed25519_verify(msg.as_ptr(), msg.len() as u32, sig.as_ptr(), pubkey.as_ptr()) == 0
ext_ed25519_verify(msg.as_ptr(), msg.len() as u32, sig.as_ptr(), pubkey.as_ref().as_ptr()) == 0
}
}