Merge pull request 'feat(dubp): add transparent BIP32 wallet' (#12) from dubp/transparent-bip32 into master

Reviewed-on: #12
This commit is contained in:
poka 2021-02-12 01:08:22 +01:00
commit 2c5237b9bc
10 changed files with 446 additions and 71 deletions

4
Cargo.lock generated
View File

@ -261,9 +261,7 @@ dependencies = [
[[package]]
name = "dup-crypto"
version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba9cba3d83bf946b9e2d3b444f98df563d6ffefa94579bdfc6f06b158cd872a9"
version = "0.41.0"
dependencies = [
"aes",
"arrayvec",

View File

@ -7,4 +7,4 @@ codegen-units = 1
debug = true
[patch.crates-io]
#dup-crypto = { path = "/home/elois/dev/duniter/libs/dubp-rs-libs/crypto" }
dup-crypto = { path = "/home/elois/dev/duniter/libs/dubp-rs-libs/crypto" }

View File

@ -10,7 +10,7 @@ crate-type = ["rlib"]
[dependencies]
allo-isolate = "0.1.6"
dup-crypto = { version = "0.40.0", features = ["bip32-ed25519", "dewif", "mnemonic", "mnemonic_french", "rand", "scrypt"] }
dup-crypto = { version = "0.41.0", features = ["bip32-ed25519", "dewif", "mnemonic", "mnemonic_french", "rand", "scrypt"] }
fast-threadpool = { version = "0.3.0", default-features = false }
once_cell = { version = "1.3.1", default-features = false, features = ["std"] }
thiserror = "1.0.23"

View File

@ -13,6 +13,9 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
pub mod bip32;
pub mod classic;
use crate::*;
pub(super) fn change_secret_code(
@ -24,23 +27,16 @@ pub(super) fn change_secret_code(
system_memory: i64,
) -> Result<Vec<String>, DubpError> {
let currency = parse_currency(currency)?;
let mut keypairs = dup_crypto::dewif::read_dewif_file_content(
ExpectedCurrency::Specific(currency),
dewif,
old_secret_code,
)
.map_err(DubpError::DewifReadError)?;
if let Some(KeyPairEnum::Ed25519(keypair)) = keypairs.next() {
let log_n = log_n(system_memory);
let new_secret_code = gen_secret_code(member_wallet, secret_code_type, log_n)?;
let new_log_n = log_n(system_memory);
let new_secret_code = gen_secret_code(member_wallet, secret_code_type, new_log_n)?;
let dewif =
dup_crypto::dewif::write_dewif_v3_content(currency, &keypair, log_n, &new_secret_code);
let pubkey = keypair.public_key().to_base58();
Ok(vec![dewif, new_secret_code, pubkey])
} else {
Err(DubpError::DewifReadError(DewifReadError::CorruptedContent))
}
let new_dewif =
dup_crypto::dewif::change_dewif_passphrase(dewif, old_secret_code, &new_secret_code)
.map_err(DubpError::DewifReadError)?;
let pubkey = get_pubkey(currency, &new_dewif, &new_secret_code)?;
Ok(vec![new_dewif, new_secret_code, pubkey])
}
pub(super) fn gen_dewif(
@ -50,17 +46,37 @@ pub(super) fn gen_dewif(
member_wallet: bool,
secret_code_type: SecretCodeType,
system_memory: i64,
wallet_type: WalletType,
) -> Result<Vec<String>, DubpError> {
let currency = parse_currency(currency)?;
let mnemonic =
Mnemonic::from_phrase(mnemonic, language).map_err(|_| DubpError::WrongLanguage)?;
let seed = dup_crypto::mnemonic::mnemonic_to_seed(&mnemonic);
let keypair = KeyPairFromSeed32Generator::generate(seed);
let log_n = log_n(system_memory);
let secret_code = gen_secret_code(member_wallet, secret_code_type, log_n)?;
let dewif = dup_crypto::dewif::write_dewif_v3_content(currency, &keypair, log_n, &secret_code);
let pubkey = keypair.public_key().to_base58();
let (dewif, pubkey) = match wallet_type {
WalletType::Ed25519 => {
let keypair = dup_crypto::keys::ed25519::bip32::KeyPair::from_seed(seed.clone());
let pubkey = keypair.public_key();
let dewif = dup_crypto::dewif::write_dewif_v4_content(
currency,
log_n,
&secret_code,
&pubkey,
seed,
);
(dewif, pubkey.to_base58())
}
WalletType::Bip32Ed25519 => {
let keypair = KeyPairFromSeed32Generator::generate(seed);
let dewif =
dup_crypto::dewif::write_dewif_v3_content(currency, &keypair, log_n, &secret_code);
(dewif, keypair.public_key().to_base58())
}
};
Ok(vec![dewif, secret_code, pubkey])
}
@ -83,54 +99,19 @@ pub(super) fn get_secret_code_len(
)?)
}
pub(super) fn get_pubkey(currency: Currency, dewif: &str, pin: &str) -> Result<String, DubpError> {
let mut keypairs = dup_crypto::dewif::read_dewif_file_content(
ExpectedCurrency::Specific(currency),
dewif,
&pin.to_ascii_uppercase(),
)
.map_err(DubpError::DewifReadError)?;
if let Some(KeyPairEnum::Ed25519(keypair)) = keypairs.next() {
Ok(keypair.public_key().to_base58())
} else {
Err(DubpError::DewifReadError(DewifReadError::CorruptedContent))
}
}
pub(super) fn sign(currency: &str, dewif: &str, pin: &str, msg: &str) -> Result<String, DubpError> {
let currency = parse_currency(currency)?;
let mut keypairs = dup_crypto::dewif::read_dewif_file_content(
ExpectedCurrency::Specific(currency),
dewif,
&pin.to_ascii_uppercase(),
)
.map_err(DubpError::DewifReadError)?;
if let Some(KeyPairEnum::Ed25519(keypair)) = keypairs.next() {
Ok(keypair.generate_signator().sign(msg.as_bytes()).to_base64())
} else {
Err(DubpError::DewifReadError(DewifReadError::CorruptedContent))
}
}
pub(super) fn sign_several(
currency: &str,
pub(super) fn get_pubkey(
currency: Currency,
dewif: &str,
pin: &str,
msgs: &[&str],
) -> Result<Vec<String>, DubpError> {
let currency = parse_currency(currency)?;
secret_code: &str,
) -> Result<String, DubpError> {
let mut keypairs = dup_crypto::dewif::read_dewif_file_content(
ExpectedCurrency::Specific(currency),
dewif,
&pin.to_ascii_uppercase(),
&secret_code.to_ascii_uppercase(),
)
.map_err(DubpError::DewifReadError)?;
if let Some(KeyPairEnum::Ed25519(keypair)) = keypairs.next() {
let signator = keypair.generate_signator();
Ok(msgs
.iter()
.map(|msg| signator.sign(msg.as_bytes()).to_base64())
.collect())
if let Some(keypair) = keypairs.next() {
Ok(keypair.public_key().to_base58())
} else {
Err(DubpError::DewifReadError(DewifReadError::CorruptedContent))
}

View File

@ -0,0 +1,97 @@
// Copyright (C) 2020 Éloïs SANCHEZ.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::*;
pub(crate) fn get_accounts_pubkeys(
currency: Currency,
dewif: &str,
secret_code: &str,
accounts_indexs: Vec<DerivationIndex>,
) -> Result<Vec<String>, DubpError> {
let mut keypairs = dup_crypto::dewif::read_dewif_file_content(
ExpectedCurrency::Specific(currency),
dewif,
&secret_code.to_ascii_uppercase(),
)
.map_err(DubpError::DewifReadError)?;
match keypairs.next() {
Some(KeyPairEnum::Bip32Ed25519(master_keypair)) => Ok(accounts_indexs
.into_iter()
.map(|account_index| {
master_keypair
.derive(account_index)
.public_key()
.to_base58()
})
.collect()),
Some(_) => Err(DubpError::NotHdWallet),
None => Err(DubpError::DewifReadError(DewifReadError::CorruptedContent)),
}
}
pub(crate) fn sign_transparent(
account_index: DerivationIndex,
currency: &str,
dewif: &str,
secret_code: &str,
msg: &str,
) -> Result<String, DubpError> {
let currency = parse_currency(currency)?;
let mut keypairs = dup_crypto::dewif::read_dewif_file_content(
ExpectedCurrency::Specific(currency),
dewif,
&secret_code.to_ascii_uppercase(),
)
.map_err(DubpError::DewifReadError)?;
match keypairs.next() {
Some(KeyPairEnum::Bip32Ed25519(master_keypair)) => Ok(master_keypair
.derive(account_index)
.generate_signator()
.sign(msg.as_bytes())
.to_base64()),
Some(_) => Err(DubpError::NotHdWallet),
None => Err(DubpError::DewifReadError(DewifReadError::CorruptedContent)),
}
}
pub(crate) fn sign_several_transparent(
account_index: DerivationIndex,
currency: &str,
dewif: &str,
secret_code: &str,
msgs: &[&str],
) -> Result<Vec<String>, DubpError> {
let currency = parse_currency(currency)?;
let mut keypairs = dup_crypto::dewif::read_dewif_file_content(
ExpectedCurrency::Specific(currency),
dewif,
&secret_code.to_ascii_uppercase(),
)
.map_err(DubpError::DewifReadError)?;
match keypairs.next() {
Some(KeyPairEnum::Bip32Ed25519(master_keypair)) => {
let signator = master_keypair.derive(account_index).generate_signator();
Ok(msgs
.iter()
.map(|msg| signator.sign(msg.as_bytes()).to_base64())
.collect())
}
Some(_) => Err(DubpError::NotHdWallet),
None => Err(DubpError::DewifReadError(DewifReadError::CorruptedContent)),
}
}

View File

@ -0,0 +1,60 @@
// Copyright (C) 2020 Éloïs SANCHEZ.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::*;
pub(crate) fn sign(
currency: &str,
dewif: &str,
secret_code: &str,
msg: &str,
) -> Result<String, DubpError> {
let currency = parse_currency(currency)?;
let mut keypairs = dup_crypto::dewif::read_dewif_file_content(
ExpectedCurrency::Specific(currency),
dewif,
&secret_code.to_ascii_uppercase(),
)
.map_err(DubpError::DewifReadError)?;
if let Some(KeyPairEnum::Ed25519(keypair)) = keypairs.next() {
Ok(keypair.generate_signator().sign(msg.as_bytes()).to_base64())
} else {
Err(DubpError::DewifReadError(DewifReadError::CorruptedContent))
}
}
pub(crate) fn sign_several(
currency: &str,
dewif: &str,
secret_code: &str,
msgs: &[&str],
) -> Result<Vec<String>, DubpError> {
let currency = parse_currency(currency)?;
let mut keypairs = dup_crypto::dewif::read_dewif_file_content(
ExpectedCurrency::Specific(currency),
dewif,
&secret_code.to_ascii_uppercase(),
)
.map_err(DubpError::DewifReadError)?;
if let Some(KeyPairEnum::Ed25519(keypair)) = keypairs.next() {
let signator = keypair.generate_signator();
Ok(msgs
.iter()
.map(|msg| signator.sign(msg.as_bytes()).to_base64())
.collect())
} else {
Err(DubpError::DewifReadError(DewifReadError::CorruptedContent))
}
}

View File

@ -13,6 +13,8 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use dup_crypto::keys::ed25519::bip32::InvalidDerivationIndex;
use crate::*;
/// Dubp error
@ -22,8 +24,14 @@ pub(crate) enum DubpError {
DewifReadError(DewifReadError),
#[error("I/O error: {0}")]
IoErr(io::Error),
#[error("{0}")]
InvalidDerivationIndex(InvalidDerivationIndex),
#[error("Digits secret code forbid for member wallet")]
DigitsCodeForbidForMemberWallet,
#[error("this wallet is not an HD wallet")]
NotHdWallet,
#[error("this account index is not a transparent account index")]
NotTransparentAccountIndex,
#[error("A given parameter is null")]
NullParamErr,
#[error("fail to generate random bytes")]
@ -44,6 +52,12 @@ impl From<io::Error> for DubpError {
}
}
impl From<InvalidDerivationIndex> for DubpError {
fn from(e: InvalidDerivationIndex) -> Self {
Self::InvalidDerivationIndex(e)
}
}
pub(crate) struct DartRes(allo_isolate::ffi::DartCObject);
impl DartRes {
pub(crate) fn err<E: ToString>(e: E) -> allo_isolate::ffi::DartCObject {

View File

@ -29,6 +29,20 @@ impl From<u32> for SecretCodeType {
}
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum WalletType {
Ed25519,
Bip32Ed25519,
}
impl From<u32> for WalletType {
fn from(i: u32) -> Self {
if i == 1 {
WalletType::Bip32Ed25519
} else {
WalletType::Ed25519
}
}
}
pub(crate) fn char_ptr_to_str<'a>(c_char_ptr: *const raw::c_char) -> Result<&'a str, DubpError> {
if c_char_ptr.is_null() {
@ -38,6 +52,16 @@ pub(crate) fn char_ptr_to_str<'a>(c_char_ptr: *const raw::c_char) -> Result<&'a
}
}
pub(crate) fn char_ptr_prt_to_vec_hard_derivation_index(
u32_ptr: *const u32,
len: u32,
) -> Result<Vec<DerivationIndex>, DubpError> {
u32_ptr_to_vec_u32(u32_ptr, len)?
.into_iter()
.map(|ai| DerivationIndex::hard(ai).map_err(DubpError::InvalidDerivationIndex))
.collect()
}
pub(crate) fn char_ptr_prt_to_vec_str<'a>(
char_ptr_ptr: *const *const raw::c_char,
len: u32,
@ -61,6 +85,24 @@ pub(crate) fn parse_currency(currency: &str) -> Result<Currency, DubpError> {
Ok(Currency::from(currency_code))
}
pub(crate) fn transparent_account_index(account_index: u32) -> Result<DerivationIndex, DubpError> {
if account_index % 3 == 0 {
DerivationIndex::hard(account_index).map_err(DubpError::InvalidDerivationIndex)
} else {
Err(DubpError::NotTransparentAccountIndex)
}
}
pub(crate) fn u32_ptr_to_vec_u32(u32_ptr: *const u32, len: u32) -> Result<Vec<u32>, DubpError> {
let len = len as usize;
let u32_slice: &[u32] = unsafe { std::slice::from_raw_parts(u32_ptr, len) };
let mut vec = Vec::with_capacity(len);
for u32_ in u32_slice {
vec.push(*u32_);
}
Ok(vec)
}
pub(crate) fn u32_to_language(i: u32) -> Result<Language, DubpError> {
match i {
0 => Ok(Language::English),

View File

@ -32,8 +32,8 @@ use dup_crypto::{
bases::b58::ToBase58,
dewif::{Currency, DewifReadError, ExpectedCurrency, G1_CURRENCY, G1_TEST_CURRENCY},
keys::{
ed25519::KeyPairFromSeed32Generator, KeyPair as _, KeyPairEnum, Signator as _,
Signature as _,
ed25519::bip32::DerivationIndex, ed25519::KeyPairFromSeed32Generator, KeyPair as _,
KeyPairEnum, Signator as _, Signature as _,
},
mnemonic::{Language, Mnemonic, MnemonicType},
};
@ -91,6 +91,7 @@ pub extern "C" fn gen_dewif(
member_wallet: u32,
secret_code_type: u32,
system_memory: i64,
wallet_type: u32,
) {
exec_async(
port,
@ -100,6 +101,7 @@ pub extern "C" fn gen_dewif(
let mnemonic = char_ptr_to_str(mnemonic)?;
let member_wallet = member_wallet != 0;
let secret_code_type = SecretCodeType::from(secret_code_type);
let wallet_type = WalletType::from(wallet_type);
Ok((
currency,
language,
@ -107,9 +109,18 @@ pub extern "C" fn gen_dewif(
member_wallet,
secret_code_type,
system_memory,
wallet_type,
))
},
|(currency, language, mnemonic, member_wallet, secret_code_type, system_memory)| {
|(
currency,
language,
mnemonic,
member_wallet,
secret_code_type,
system_memory,
wallet_type,
)| {
dewif::gen_dewif(
currency,
language,
@ -117,6 +128,7 @@ pub extern "C" fn gen_dewif(
member_wallet,
secret_code_type,
system_memory,
wallet_type,
)
},
)
@ -201,6 +213,31 @@ pub extern "C" fn get_dewif_pubkey(
}
#[no_mangle]
pub extern "C" fn get_bip32_dewif_accounts_pubkeys(
port: i64,
currency: *const raw::c_char,
dewif: *const raw::c_char,
secret_code: *const raw::c_char,
accounts_indexs_len: u32,
accounts_indexs: *const u32,
) {
exec_async(
port,
|| {
let currency = parse_currency(char_ptr_to_str(currency)?)?;
let dewif = char_ptr_to_str(dewif)?;
let secret_code = char_ptr_to_str(secret_code)?;
let accounts_indexs =
char_ptr_prt_to_vec_hard_derivation_index(accounts_indexs, accounts_indexs_len)?;
Ok((currency, dewif, secret_code, accounts_indexs))
},
|(currency, dewif, secret_code, accounts_indexs)| {
dewif::bip32::get_accounts_pubkeys(currency, dewif, secret_code, accounts_indexs)
},
)
}
#[no_mangle]
pub extern "C" fn get_legacy_pubkey(
port: i64,
salt: *const raw::c_char,
@ -251,7 +288,32 @@ pub extern "C" fn sign(
let msg = char_ptr_to_str(msg)?;
Ok((currency, dewif, pin, msg))
},
|(currency, dewif, pin, msg)| dewif::sign(currency, dewif, pin, msg),
|(currency, dewif, pin, msg)| dewif::classic::sign(currency, dewif, pin, msg),
)
}
#[no_mangle]
pub extern "C" fn sign_bip32_transparent(
port: i64,
account_index: u32,
currency: *const raw::c_char,
dewif: *const raw::c_char,
secret_code: *const raw::c_char,
msg: *const raw::c_char,
) {
exec_async(
port,
|| {
let account_index = transparent_account_index(account_index)?;
let currency = char_ptr_to_str(currency)?;
let dewif = char_ptr_to_str(dewif)?;
let pin = char_ptr_to_str(secret_code)?;
let msg = char_ptr_to_str(msg)?;
Ok((currency, dewif, pin, msg, account_index))
},
|(currency, dewif, secret_code, msg, account_index)| {
dewif::bip32::sign_transparent(account_index, currency, dewif, secret_code, msg)
},
)
}
@ -292,6 +354,32 @@ pub extern "C" fn sign_several(
let msgs = char_ptr_prt_to_vec_str(msgs, msgs_len)?;
Ok((currency, dewif, pin, msgs))
},
|(currency, dewif, pin, msgs)| dewif::sign_several(currency, dewif, pin, &msgs),
|(currency, dewif, pin, msgs)| dewif::classic::sign_several(currency, dewif, pin, &msgs),
)
}
#[no_mangle]
pub extern "C" fn sign_several_bip32_transparent(
port: i64,
account_index: u32,
currency: *const raw::c_char,
dewif: *const raw::c_char,
pin: *const raw::c_char,
msgs_len: u32,
msgs: *const *const raw::c_char,
) {
exec_async(
port,
|| {
let account_index = transparent_account_index(account_index)?;
let currency = char_ptr_to_str(currency)?;
let dewif = char_ptr_to_str(dewif)?;
let pin = char_ptr_to_str(pin)?;
let msgs = char_ptr_prt_to_vec_str(msgs, msgs_len)?;
Ok((currency, dewif, pin, msgs, account_index))
},
|(currency, dewif, pin, msgs, account_index)| {
dewif::bip32::sign_several_transparent(account_index, currency, dewif, pin, &msgs)
},
)
}

View File

@ -38,6 +38,15 @@ enum SecretCodeType {
letters,
}
/// Wallet type
enum WalletType {
/// Ed25519
ed25519,
/// BIP32-Ed25519
bip32Ed25519,
}
/// DUBP Rust utilities
///
/// All the functions of this package are static methods of this
@ -128,6 +137,7 @@ class DubpRust {
Language language = Language.english,
String mnemonic,
SecretCodeType secretCodeType = SecretCodeType.letters,
WalletType walletType = WalletType.ed25519,
}) async {
int ram = SysInfo.getTotalPhysicalMemory();
print('ram=$ram');
@ -143,12 +153,34 @@ class DubpRust {
0,
secretCodeType.index,
ram,
walletType.index,
);
List<String> newWallet = await completer.future;
return Future.value(NewWallet._(newWallet[0], newWallet[1], newWallet[2]));
}
//get_bip32_dewif_accounts_pubkeys
/// Get BIP32 accounts public keys (in base 58) of `dewif` master keypair.
static Future<String> getBip32DewifAccountsPublicKeys(
{String currency = "g1",
String dewif,
String secretCode,
List<int> accountsIndex}) async {
final completer = Completer<String>();
final sendPort =
singleCompletePort<String, String>(completer, callback: _handleErr);
native.get_bip32_dewif_accounts_pubkeys(
sendPort.nativePort,
Utf8.toUtf8(currency),
Utf8.toUtf8(dewif),
Utf8.toUtf8(secretCode),
accountsIndex.length,
_listIntToPtr(accountsIndex));
return completer.future;
}
/// Get public key (in base 58) of `dewif` keypair.
static Future<String> getDewifPublicKey(
{String currency = "g1", String dewif, String pin}) async {
@ -215,6 +247,31 @@ class DubpRust {
return completer.future;
}
/// Sign the message `message` with `dewif` Bip32-Ed25519 keypair encryted
/// in DEWIF format.
///
/// If you have several messages to sign, use `signSeveralBip32Transparent`
/// method instead.
static Future<String> signBip32Transparent(
{int accountIndex,
String currency = "g1",
String dewif,
String secretCode,
String message}) {
final completer = Completer<String>();
final sendPort =
singleCompletePort<String, String>(completer, callback: _handleErr);
native.sign_bip32_transparent(
sendPort.nativePort,
accountIndex,
Utf8.toUtf8(currency),
Utf8.toUtf8(dewif),
Utf8.toUtf8(secretCode),
Utf8.toUtf8(message),
);
return completer.future;
}
/// Sign the message `message` with legacy wallet (password + salt)
///
/// This deprecated method must be used only for compatibility purpose !
@ -259,6 +316,44 @@ class DubpRust {
return completer.future;
}
/// Sign several messages `messages` with `dewif` keypair encryted in DEWIF
/// format.
///
/// This method is optimized to sign several messages at once. If you have
/// several messages to sign, avoid calling the `sign` method for each
/// message. Use this `signSeveral` method instead.
static Future<List<String>> signSeveralBip32Transparent(
{int accountIndex,
String currency = "g1",
String dewif,
String pin,
List<String> messages}) {
final completer = Completer<List<String>>();
final sendPort = singleCompletePort<List<String>, List>(completer,
callback: _handleErrList);
native.sign_several_bip32_transparent(
sendPort.nativePort,
accountIndex,
Utf8.toUtf8(currency),
Utf8.toUtf8(dewif),
Utf8.toUtf8(pin),
messages.length,
_listStringToPtr(messages),
);
return completer.future;
}
static Pointer<Uint32> _listIntToPtr(List<int> list) {
//final listUint32 = list.map(int.toUnsigned).toList();
final Pointer<Uint32> ptr = allocate(count: list.length);
for (var i = 0; i < list.length; i++) {
ptr[i] = list[i];
}
return ptr;
}
static Pointer<Pointer<Utf8>> _listStringToPtr(List<String> list) {
final listUtf8 = list.map(Utf8.toUtf8).toList();
final Pointer<Pointer<Utf8>> ptr = allocate(count: listUtf8.length);