mirror of
https://github.com/mimblewimble/grin.git
synced 2025-02-01 17:01:09 +03:00
refactor/rework key derivation (#652)
seed -> ext_key -> many child_keys
This commit is contained in:
parent
2def215553
commit
5a7d22977e
3 changed files with 105 additions and 93 deletions
|
@ -1057,7 +1057,7 @@ mod test {
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
let short_id = input.short_id(&block_hash);
|
let short_id = input.short_id(&block_hash);
|
||||||
assert_eq!(short_id, ShortId::from_hex("102864956811").unwrap());
|
assert_eq!(short_id, ShortId::from_hex("3e1262905b7a").unwrap());
|
||||||
|
|
||||||
// now generate the short_id for a *very* similar output (single feature flag different)
|
// now generate the short_id for a *very* similar output (single feature flag different)
|
||||||
// and check it generates a different short_id
|
// and check it generates a different short_id
|
||||||
|
@ -1072,6 +1072,6 @@ mod test {
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
let short_id = input.short_id(&block_hash);
|
let short_id = input.short_id(&block_hash);
|
||||||
assert_eq!(short_id, ShortId::from_hex("b8c189165df1").unwrap());
|
assert_eq!(short_id, ShortId::from_hex("90653c1c870a").unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,12 +126,20 @@ impl Identifier {
|
||||||
Identifier(identifier)
|
Identifier(identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_key_id(secp: &Secp256k1, pubkey: &PublicKey) -> Identifier {
|
pub fn from_pubkey(secp: &Secp256k1, pubkey: &PublicKey) -> Identifier {
|
||||||
let bytes = pubkey.serialize_vec(secp, true);
|
let bytes = pubkey.serialize_vec(secp, true);
|
||||||
let identifier = blake2b(IDENTIFIER_SIZE, &[], &bytes[..]);
|
let identifier = blake2b(IDENTIFIER_SIZE, &[], &bytes[..]);
|
||||||
Identifier::from_bytes(&identifier.as_bytes())
|
Identifier::from_bytes(&identifier.as_bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the identifier of the secret key
|
||||||
|
/// which is the blake2b (10 byte) digest of the PublicKey
|
||||||
|
/// corresponding to the secret key provided.
|
||||||
|
fn from_secret_key(secp: &Secp256k1, key: &SecretKey) -> Result<Identifier, Error> {
|
||||||
|
let key_id = PublicKey::from_secret_key(secp, key)?;
|
||||||
|
Ok(Identifier::from_pubkey(secp, &key_id))
|
||||||
|
}
|
||||||
|
|
||||||
fn from_hex(hex: &str) -> Result<Identifier, Error> {
|
fn from_hex(hex: &str) -> Result<Identifier, Error> {
|
||||||
let bytes = util::from_hex(hex.to_string()).unwrap();
|
let bytes = util::from_hex(hex.to_string()).unwrap();
|
||||||
Ok(Identifier::from_bytes(&bytes))
|
Ok(Identifier::from_bytes(&bytes))
|
||||||
|
@ -162,6 +170,20 @@ impl fmt::Display for Identifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ChildKey {
|
||||||
|
/// Child number of the key (n derivations)
|
||||||
|
pub n_child: u32,
|
||||||
|
/// Root key id
|
||||||
|
pub root_key_id: Identifier,
|
||||||
|
/// Key id
|
||||||
|
pub key_id: Identifier,
|
||||||
|
/// The private key
|
||||||
|
pub key: SecretKey,
|
||||||
|
/// The key used for generating the associated switch_commit_hash
|
||||||
|
pub switch_key: [u8; 32],
|
||||||
|
}
|
||||||
|
|
||||||
/// An ExtendedKey is a secret key which can be used to derive new
|
/// An ExtendedKey is a secret key which can be used to derive new
|
||||||
/// secret keys to blind the commitment of a transaction output.
|
/// secret keys to blind the commitment of a transaction output.
|
||||||
/// To be usable, a secret key should have an amount assigned to it,
|
/// To be usable, a secret key should have an amount assigned to it,
|
||||||
|
@ -169,20 +191,20 @@ impl fmt::Display for Identifier {
|
||||||
/// given.
|
/// given.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ExtendedKey {
|
pub struct ExtendedKey {
|
||||||
/// Depth of the extended key
|
/// Child number of the extended key
|
||||||
pub depth: u8,
|
|
||||||
/// Child number of the key
|
|
||||||
pub n_child: u32,
|
pub n_child: u32,
|
||||||
/// Root key identifier
|
/// Root key id
|
||||||
pub root_key_id: Identifier,
|
pub root_key_id: Identifier,
|
||||||
/// Actual private key
|
/// Key id
|
||||||
|
pub key_id: Identifier,
|
||||||
|
/// The secret key
|
||||||
pub key: SecretKey,
|
pub key: SecretKey,
|
||||||
/// Code of the derivation chain
|
/// The chain code for the key derivation chain
|
||||||
pub chaincode: [u8; 32],
|
pub chain_code: [u8; 32],
|
||||||
/// The bytes of the key used for generating the associated switch_commit_hash
|
/// The key used for generating the associated switch_commit_hash
|
||||||
pub switch_key: [u8; 32],
|
pub switch_key: [u8; 32],
|
||||||
/// Code of the derivation chain for the switch_commit_hash key
|
/// The chain code for the switch key derivation chain
|
||||||
pub switch_chaincode: [u8; 32],
|
pub switch_chain_code: [u8; 32],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExtendedKey {
|
impl ExtendedKey {
|
||||||
|
@ -196,14 +218,15 @@ impl ExtendedKey {
|
||||||
let derived = blake2b(64, b"Grin/MW Seed", seed);
|
let derived = blake2b(64, b"Grin/MW Seed", seed);
|
||||||
let slice = derived.as_bytes();
|
let slice = derived.as_bytes();
|
||||||
|
|
||||||
// TODO Error handling
|
|
||||||
let key = SecretKey::from_slice(&secp, &slice[0..32])
|
let key = SecretKey::from_slice(&secp, &slice[0..32])
|
||||||
.expect("Error generating from seed");
|
.expect("Error deriving key (from_slice)");
|
||||||
|
|
||||||
let mut chaincode: [u8; 32] = Default::default();
|
let mut chain_code: [u8; 32] = Default::default();
|
||||||
(&mut chaincode).copy_from_slice(&slice[32..64]);
|
(&mut chain_code).copy_from_slice(&slice[32..64]);
|
||||||
|
|
||||||
// Now derive the switch_key and switch_chaincode in a similar fashion
|
let key_id = Identifier::from_secret_key(secp, &key)?;
|
||||||
|
|
||||||
|
// Now derive the switch_key and switch_chain_code in a similar fashion
|
||||||
// but using a different key to ensure there is nothing linking
|
// but using a different key to ensure there is nothing linking
|
||||||
// the secret key and the switch commit hash key for any extended key
|
// the secret key and the switch commit hash key for any extended key
|
||||||
// we subsequently derive
|
// we subsequently derive
|
||||||
|
@ -212,75 +235,67 @@ impl ExtendedKey {
|
||||||
|
|
||||||
let mut switch_key: [u8; 32] = Default::default();
|
let mut switch_key: [u8; 32] = Default::default();
|
||||||
(&mut switch_key).copy_from_slice(&switch_slice[0..32]);
|
(&mut switch_key).copy_from_slice(&switch_slice[0..32]);
|
||||||
let mut switch_chaincode: [u8; 32] = Default::default();
|
|
||||||
(&mut switch_chaincode).copy_from_slice(&switch_slice[32..64]);
|
|
||||||
|
|
||||||
let mut ext_key = ExtendedKey {
|
let mut switch_chain_code: [u8; 32] = Default::default();
|
||||||
depth: 0,
|
(&mut switch_chain_code).copy_from_slice(&switch_slice[32..64]);
|
||||||
root_key_id: Identifier::zero(),
|
|
||||||
|
let ext_key = ExtendedKey {
|
||||||
n_child: 0,
|
n_child: 0,
|
||||||
key,
|
root_key_id: key_id.clone(),
|
||||||
chaincode,
|
key_id: key_id.clone(),
|
||||||
switch_key,
|
|
||||||
switch_chaincode,
|
|
||||||
};
|
|
||||||
|
|
||||||
ext_key.root_key_id = ext_key.identifier(secp)?;
|
// key and extended chain code for the key itself
|
||||||
|
key,
|
||||||
|
chain_code,
|
||||||
|
|
||||||
|
// key and extended chain code for the key for hashed switch commitments
|
||||||
|
switch_key,
|
||||||
|
switch_chain_code,
|
||||||
|
};
|
||||||
|
|
||||||
Ok(ext_key)
|
Ok(ext_key)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the identifier of the key
|
/// Derive a child key from this extended key
|
||||||
/// which is the blake2b (10 byte) digest of the PublicKey
|
pub fn derive(&self, secp: &Secp256k1, n: u32) -> Result<ChildKey, Error> {
|
||||||
// corresponding to the underlying SecretKey
|
|
||||||
pub fn identifier(&self, secp: &Secp256k1) -> Result<Identifier, Error> {
|
|
||||||
let key_id = PublicKey::from_secret_key(secp, &self.key)?;
|
|
||||||
Ok(Identifier::from_key_id(secp, &key_id))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Derive an extended key from an extended key
|
|
||||||
pub fn derive(&self, secp: &Secp256k1, n: u32) -> Result<ExtendedKey, Error> {
|
|
||||||
let mut n_bytes: [u8; 4] = [0; 4];
|
let mut n_bytes: [u8; 4] = [0; 4];
|
||||||
BigEndian::write_u32(&mut n_bytes, n);
|
BigEndian::write_u32(&mut n_bytes, n);
|
||||||
|
|
||||||
let mut seed = self.key[..].to_vec();
|
let mut seed = self.key[..].to_vec();
|
||||||
seed.extend_from_slice(&n_bytes);
|
seed.extend_from_slice(&n_bytes);
|
||||||
|
|
||||||
let derived = blake2b(64, &self.chaincode[..], &seed[..]);
|
// only need a 32 byte digest here as we only need the bytes for the key itself
|
||||||
let slice = derived.as_bytes();
|
// we do not need additional bytes for a derived (and unused) chain code
|
||||||
|
let derived = blake2b(32, &self.chain_code[..], &seed[..]);
|
||||||
|
|
||||||
let mut key = SecretKey::from_slice(&secp, &slice[0..32])
|
let mut key = SecretKey::from_slice(&secp, &derived.as_bytes()[..])
|
||||||
.expect("Error deriving key");
|
.expect("Error deriving key (from_slice)");
|
||||||
key.add_assign(secp, &self.key)
|
key.add_assign(secp, &self.key)
|
||||||
.expect("Error deriving key");
|
.expect("Error deriving key (add_assign)");
|
||||||
|
|
||||||
let mut chaincode: [u8; 32] = Default::default();
|
let key_id = Identifier::from_secret_key(secp, &key)?;
|
||||||
(&mut chaincode).copy_from_slice(&slice[32..64]);
|
|
||||||
|
|
||||||
// Now derive the switch_key and switch_chaincode in a similar fashion
|
|
||||||
let mut switch_seed = self.switch_key[..].to_vec();
|
let mut switch_seed = self.switch_key[..].to_vec();
|
||||||
switch_seed.extend_from_slice(&n_bytes);
|
switch_seed.extend_from_slice(&n_bytes);
|
||||||
let switch_derived = blake2b(64, &self.switch_chaincode[..], &switch_seed[..]);
|
|
||||||
let switch_slice = switch_derived.as_bytes();
|
// only need a 32 byte digest here as we only need the bytes for the key itself
|
||||||
|
// we do not need additional bytes for a derived (and unused) chain code
|
||||||
|
let switch_derived = blake2b(32, &self.switch_chain_code[..], &switch_seed[..]);
|
||||||
|
|
||||||
let mut switch_key: [u8; 32] = Default::default();
|
let mut switch_key: [u8; 32] = Default::default();
|
||||||
(&mut switch_key).copy_from_slice(&switch_slice[0..32]);
|
(&mut switch_key).copy_from_slice(&switch_derived.as_bytes()[..]);
|
||||||
let mut switch_chaincode: [u8; 32] = Default::default();
|
|
||||||
(&mut switch_chaincode).copy_from_slice(&switch_slice[32..64]);
|
|
||||||
|
|
||||||
// TODO check if key != 0 ?
|
Ok(ChildKey {
|
||||||
|
|
||||||
Ok(ExtendedKey {
|
|
||||||
depth: self.depth + 1,
|
|
||||||
root_key_id: self.identifier(&secp)?,
|
|
||||||
n_child: n,
|
n_child: n,
|
||||||
|
root_key_id: self.root_key_id.clone(),
|
||||||
|
key_id,
|
||||||
key,
|
key,
|
||||||
chaincode,
|
|
||||||
switch_key,
|
switch_key,
|
||||||
switch_chaincode,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use serde_json;
|
use serde_json;
|
||||||
|
@ -321,22 +336,20 @@ mod test {
|
||||||
let extk = ExtendedKey::from_seed(&s, &seed.as_slice()).unwrap();
|
let extk = ExtendedKey::from_seed(&s, &seed.as_slice()).unwrap();
|
||||||
let sec = from_hex("2878a92133b0a7c2fbfb0bd4520ed2e55ea3fa2913200f05c30077d30b193480");
|
let sec = from_hex("2878a92133b0a7c2fbfb0bd4520ed2e55ea3fa2913200f05c30077d30b193480");
|
||||||
let secret_key = SecretKey::from_slice(&s, sec.as_slice()).unwrap();
|
let secret_key = SecretKey::from_slice(&s, sec.as_slice()).unwrap();
|
||||||
let chaincode =
|
let chain_code =
|
||||||
from_hex("3ad40dd836c5ce25dfcbdee5044d92cf6b65bd5475717fa7a56dd4a032cca7c0");
|
from_hex("3ad40dd836c5ce25dfcbdee5044d92cf6b65bd5475717fa7a56dd4a032cca7c0");
|
||||||
let identifier = from_hex("6f7c1a053ca54592e783");
|
let identifier = from_hex("6f7c1a053ca54592e783");
|
||||||
let depth = 0;
|
|
||||||
let n_child = 0;
|
let n_child = 0;
|
||||||
assert_eq!(extk.key, secret_key);
|
assert_eq!(extk.key, secret_key);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
extk.identifier(&s).unwrap(),
|
extk.key_id,
|
||||||
Identifier::from_bytes(identifier.as_slice())
|
Identifier::from_bytes(identifier.as_slice())
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
extk.root_key_id,
|
extk.root_key_id,
|
||||||
Identifier::from_bytes(identifier.as_slice())
|
Identifier::from_bytes(identifier.as_slice())
|
||||||
);
|
);
|
||||||
assert_eq!(extk.chaincode, chaincode.as_slice());
|
assert_eq!(extk.chain_code, chain_code.as_slice());
|
||||||
assert_eq!(extk.depth, depth);
|
|
||||||
assert_eq!(extk.n_child, n_child);
|
assert_eq!(extk.n_child, n_child);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -346,25 +359,20 @@ mod test {
|
||||||
let seed = from_hex("000102030405060708090a0b0c0d0e0f");
|
let seed = from_hex("000102030405060708090a0b0c0d0e0f");
|
||||||
let extk = ExtendedKey::from_seed(&s, &seed.as_slice()).unwrap();
|
let extk = ExtendedKey::from_seed(&s, &seed.as_slice()).unwrap();
|
||||||
let derived = extk.derive(&s, 0).unwrap();
|
let derived = extk.derive(&s, 0).unwrap();
|
||||||
let sec = from_hex("2676a3ab2ded7c79cbd0bd26d448698de5da5af8e809080d3cacfa2ee31a9aa7");
|
let sec = from_hex("55f1a2b67ec58933bf954fdc721327afe486e8989af923c3ae298e45a84ef597");
|
||||||
let secret_key = SecretKey::from_slice(&s, sec.as_slice()).unwrap();
|
let secret_key = SecretKey::from_slice(&s, sec.as_slice()).unwrap();
|
||||||
let chaincode =
|
|
||||||
from_hex("9bc90b148f4c9478205d6ca72c58bbda2902be1e5082de05d56339a74a5314a3");
|
|
||||||
let root_key_id = from_hex("6f7c1a053ca54592e783");
|
let root_key_id = from_hex("6f7c1a053ca54592e783");
|
||||||
let identifier = from_hex("5f2ec8ee00e8bca002fa");
|
let identifier = from_hex("8fa188b56cefe66be154");
|
||||||
let depth = 1;
|
|
||||||
let n_child = 0;
|
let n_child = 0;
|
||||||
assert_eq!(derived.key, secret_key);
|
assert_eq!(derived.key, secret_key);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
derived.identifier(&s).unwrap(),
|
derived.key_id,
|
||||||
Identifier::from_bytes(identifier.as_slice())
|
Identifier::from_bytes(identifier.as_slice())
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
derived.root_key_id,
|
derived.root_key_id,
|
||||||
Identifier::from_bytes(root_key_id.as_slice())
|
Identifier::from_bytes(root_key_id.as_slice())
|
||||||
);
|
);
|
||||||
assert_eq!(derived.chaincode, chaincode.as_slice());
|
|
||||||
assert_eq!(derived.depth, depth);
|
|
||||||
assert_eq!(derived.n_child, n_child);
|
assert_eq!(derived.n_child, n_child);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,9 +106,8 @@ impl Keychain {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn derive_key_id(&self, derivation: u32) -> Result<Identifier, Error> {
|
pub fn derive_key_id(&self, derivation: u32) -> Result<Identifier, Error> {
|
||||||
let extkey = self.extkey.derive(&self.secp, derivation)?;
|
let child_key = self.extkey.derive(&self.secp, derivation)?;
|
||||||
let key_id = extkey.identifier(&self.secp)?;
|
Ok(child_key.key_id)
|
||||||
Ok(key_id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn derived_key(&self, key_id: &Identifier) -> Result<SecretKey, Error> {
|
fn derived_key(&self, key_id: &Identifier) -> Result<SecretKey, Error> {
|
||||||
|
@ -118,11 +117,11 @@ impl Keychain {
|
||||||
return Ok(*key);
|
return Ok(*key);
|
||||||
}
|
}
|
||||||
|
|
||||||
let extkey = self.derived_extended_key(key_id)?;
|
let child_key = self.derived_child_key(key_id)?;
|
||||||
Ok(extkey.key)
|
Ok(child_key.key)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn derived_extended_key(&self, key_id: &Identifier) -> Result<extkey::ExtendedKey, Error> {
|
fn derived_child_key(&self, key_id: &Identifier) -> Result<extkey::ChildKey, Error> {
|
||||||
trace!(LOGGER, "Derived Key by key_id: {}", key_id);
|
trace!(LOGGER, "Derived Key by key_id: {}", key_id);
|
||||||
|
|
||||||
// then check the derivation cache to see if we have previously derived this key
|
// then check the derivation cache to see if we have previously derived this key
|
||||||
|
@ -142,22 +141,27 @@ impl Keychain {
|
||||||
{
|
{
|
||||||
let mut cache = self.key_derivation_cache.write().unwrap();
|
let mut cache = self.key_derivation_cache.write().unwrap();
|
||||||
for i in 1..100_000 {
|
for i in 1..100_000 {
|
||||||
let extkey = self.extkey.derive(&self.secp, i)?;
|
let child_key = self.extkey.derive(&self.secp, i)?;
|
||||||
let extkey_id = extkey.identifier(&self.secp)?;
|
// let child_key_id = extkey.identifier(&self.secp)?;
|
||||||
|
|
||||||
if !cache.contains_key(&extkey_id) {
|
if !cache.contains_key(&child_key.key_id) {
|
||||||
trace!(LOGGER, "... Derived Key (cache miss) key_id: {}, derivation: {}", extkey_id, extkey.n_child);
|
trace!(
|
||||||
cache.insert(extkey_id.clone(), extkey.n_child);
|
LOGGER,
|
||||||
|
"... Derived Key (cache miss) key_id: {}, derivation: {}",
|
||||||
|
child_key.key_id,
|
||||||
|
child_key.n_child,
|
||||||
|
);
|
||||||
|
cache.insert(child_key.key_id.clone(), child_key.n_child);
|
||||||
}
|
}
|
||||||
|
|
||||||
if extkey_id == *key_id {
|
if child_key.key_id == *key_id {
|
||||||
return Ok(extkey);
|
return Ok(child_key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(Error::KeyDerivation(
|
Err(Error::KeyDerivation(
|
||||||
format!("cannot find extkey for {:?}", key_id),
|
format!("failed to derive child_key for {:?}", key_id),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,10 +169,10 @@ impl Keychain {
|
||||||
fn derived_key_from_index(
|
fn derived_key_from_index(
|
||||||
&self,
|
&self,
|
||||||
derivation: u32,
|
derivation: u32,
|
||||||
) -> Result<extkey::ExtendedKey, Error> {
|
) -> Result<extkey::ChildKey, Error> {
|
||||||
trace!(LOGGER, "Derived Key (fast) by derivation: {}", derivation);
|
trace!(LOGGER, "Derived Key (fast) by derivation: {}", derivation);
|
||||||
let extkey = self.extkey.derive(&self.secp, derivation)?;
|
let child_key = self.extkey.derive(&self.secp, derivation)?;
|
||||||
return Ok(extkey)
|
return Ok(child_key)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn commit(&self, amount: u64, key_id: &Identifier) -> Result<Commitment, Error> {
|
pub fn commit(&self, amount: u64, key_id: &Identifier) -> Result<Commitment, Error> {
|
||||||
|
@ -182,8 +186,8 @@ impl Keychain {
|
||||||
amount: u64,
|
amount: u64,
|
||||||
derivation: u32,
|
derivation: u32,
|
||||||
) -> Result<Commitment, Error> {
|
) -> Result<Commitment, Error> {
|
||||||
let extkey = self.derived_key_from_index(derivation)?;
|
let child_key = self.derived_key_from_index(derivation)?;
|
||||||
let commit = self.secp.commit(amount, extkey.key)?;
|
let commit = self.secp.commit(amount, child_key.key)?;
|
||||||
Ok(commit)
|
Ok(commit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,8 +214,8 @@ impl Keychain {
|
||||||
return Ok(key);
|
return Ok(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
let extkey = self.derived_extended_key(key_id)?;
|
let child_key = self.derived_child_key(key_id)?;
|
||||||
Ok(extkey.switch_key)
|
Ok(child_key.switch_key)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn range_proof(
|
pub fn range_proof(
|
||||||
|
|
Loading…
Reference in a new issue