Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions NBitcoin/Scripting/AddressDescriptor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System.Collections.Generic;

namespace NBitcoin.Scripting
{
/** A parsed addr(A) descriptor. */
public class AddressDescriptor : DescriptorImpl
{
private IDestination m_destination;

protected string ToStringExtra() { return EncodeDestination(m_destination); }
protected List<Script> MakeScripts(List<PubKey> pubkeys, Script script, FlatSigningProvider signingProvider) { return Vector(GetScriptForDestination(m_destination)); }

public AddressDescriptor(IDestination destination)
: base(null, null, "addr")
{
m_destination = destination;
}
public bool IsSolvable(){ return false; }

public Optional<OutputType> GetOutputType()
{
switch (m_destination.which()) {
case 1 /* PKHash */:
case 2 /* ScriptHash */: return OutputType::LEGACY;
case 3 /* WitnessV0ScriptHash */:
case 4 /* WitnessV0KeyHash */:
case 5 /* WitnessUnknown */: return OutputType::BECH32;
case 0 /* CNoDestination */:
default: return nullopt;
}
}
public bool IsSingleType(){ return true; }
}
}
149 changes: 149 additions & 0 deletions NBitcoin/Scripting/BIP32PubkeyProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
namespace NBitcoin.Scripting
{
/** An object representing a parsed extended public key in a descriptor. */
public class BIP32PubkeyProvider : PubkeyProvider
{
// Root xpub, path, and final derivation step type being used, if any
private ExtPubKey m_root_extkey;
private KeyPath m_path;
private DeriveType m_derive;
// Cache of the parent of the final derived pubkeys.
// Primarily useful for situations when no read_cache is provided
private ExtPubKey m_cached_xpub;

private bool GetExtKey(SigningProvider arg, ExtKey ret)
{
Key key;
if (!arg.GetKey(m_root_extkey.pubkey.GetID(), key)) return false;
ret.nDepth = m_root_extkey.nDepth;
std::copy(m_root_extkey.vchFingerprint, m_root_extkey.vchFingerprint + sizeof(ret.vchFingerprint), ret.vchFingerprint);
ret.nChild = m_root_extkey.nChild;
ret.chaincode = m_root_extkey.chaincode;
ret.key = key;
return true;
}

// Derives the last xprv
private bool GetDerivedExtKey(SigningProvider arg, ExtKey xprv)
{
if (!GetExtKey(arg, xprv)) return false;
foreach (var entry in m_path) {
xprv.Derive(xprv, entry);
}
return true;
}

private bool IsHardened()
{
if (m_derive == DeriveType::HARDENED) return true;
foreach (var entry in m_path) {
if (entry >> 31) return true;
}
return false;
}

public BIP32PubkeyProvider(uint exp_index, ExtPubKey extkey, KeyPath path, DeriveType derive)
: base(exp_index)
{
m_root_extkey = extkey;
m_path = path;
m_derive = derive;
}
public bool IsRange() { return m_derive != DeriveType::NO; }
public uint GetSize() { return 33; }
public bool GetPubKey(int pos, SigningProvider arg, PubKey key_out, KeyOriginInfo final_info_out, DescriptorCache read_cache = null, DescriptorCache write_cache = null)
{
// Info of parent of the to be derived pubkey
KeyOriginInfo parent_info;
KeyID keyid = m_root_extkey.pubkey.GetID();
std::copy(keyid.begin(), keyid.begin() + sizeof(parent_info.fingerprint), parent_info.fingerprint);
parent_info.path = m_path;

// Info of the derived key itself which is copied out upon successful completion
KeyOriginInfo final_info_out_tmp = parent_info;
if (m_derive == DeriveType::UNHARDENED) final_info_out_tmp.path.push_back((uint32_t)pos);
if (m_derive == DeriveType::HARDENED) final_info_out_tmp.path.push_back(((uint32_t)pos) | 0x80000000L);

// Derive keys or fetch them from cache
ExtPubKey final_extkey = m_root_extkey;
ExtPubKey parent_extkey = m_root_extkey;
bool der = true;
if (read_cache) {
if (!read_cache.GetCachedDerivedExtPubKey(m_expr_index, pos, final_extkey)) {
if (m_derive == DeriveType::HARDENED) return false;
// Try to get the derivation parent
if (!read_cache.GetCachedParentExtPubKey(m_expr_index, parent_extkey)) return false;
final_extkey = parent_extkey;
if (m_derive == DeriveType::UNHARDENED) der = parent_extkey.Derive(final_extkey, pos);
}
} else if (m_cached_xpub.pubkey.IsValid() && m_derive != DeriveType::HARDENED) {
parent_extkey = final_extkey = m_cached_xpub;
if (m_derive == DeriveType::UNHARDENED) der = parent_extkey.Derive(final_extkey, pos);
} else if (IsHardened()) {
ExtKey xprv;
if (!GetDerivedExtKey(arg, xprv)) return false;
parent_extkey = xprv.Neuter();
if (m_derive == DeriveType::UNHARDENED) der = xprv.Derive(xprv, pos);
if (m_derive == DeriveType::HARDENED) der = xprv.Derive(xprv, pos | 0x80000000UL);
final_extkey = xprv.Neuter();
} else {
foreach (var entry in m_path) {
der = parent_extkey.Derive(parent_extkey, entry);
assert(der);
}
final_extkey = parent_extkey;
if (m_derive == DeriveType::UNHARDENED) der = parent_extkey.Derive(final_extkey, pos);
assert(m_derive != DeriveType::HARDENED);
}
assert(der);

final_info_out = final_info_out_tmp;
key_out = final_extkey.pubkey;

// We rely on the consumer to check that m_derive isn't HARDENED as above
// But we can't have already cached something in case we read something from the cache
// and parent_extkey isn't actually the parent.
if (!m_cached_xpub.pubkey.IsValid()) m_cached_xpub = parent_extkey;

if (write_cache) {
// Only cache parent if there is any unhardened derivation
if (m_derive != DeriveType::HARDENED) {
write_cache.CacheParentExtPubKey(m_expr_index, parent_extkey);
} else if (final_info_out.path.size() > 0) {
write_cache.CacheDerivedExtPubKey(m_expr_index, pos, final_extkey);
}
}

return true;
}
public override string ToString()
{
string ret = EncodeExtPubKey(m_root_extkey) + FormatHDKeypath(m_path);
if (IsRange()) {
ret += "/*";
if (m_derive == DeriveType::HARDENED) ret += '\'';
}
return ret;
}
public bool ToPrivateString(SigningProvider arg, out string output)
{
ExtKey key;
if (!GetExtKey(arg, key)) return false;
output = EncodeExtKey(key) + FormatHDKeypath(m_path);
if (IsRange()) {
output += "/*";
if (m_derive == DeriveType::HARDENED) output += '\'';
}
return true;
}
public bool GetPrivKey(int pos, SigningProvider arg, out Key key)
{
ExtKey extkey;
if (!GetDerivedExtKey(arg, extkey)) return false;
if (m_derive == DeriveType::UNHARDENED) extkey.Derive(extkey, pos);
if (m_derive == DeriveType::HARDENED) extkey.Derive(extkey, pos | 0x80000000UL);
key = extkey.key;
return true;
}
}
}
27 changes: 27 additions & 0 deletions NBitcoin/Scripting/ComboDescriptor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.Collections.Generic;

namespace NBitcoin.Scripting
{
/** A parsed combo(P) descriptor. */
class ComboDescriptor :DescriptorImpl
{
protected List<Script> MakeScripts(List<PubKey> keys, Script script, out FlatSigningProvider output)
{
List<Script> ret;
KeyID id = keys[0].GetID();
output.pubkeys.emplace(id, keys[0]);
ret.emplace_back(GetScriptForRawPubKey(keys[0])); // P2PK
ret.emplace_back(GetScriptForDestination(PKHash(id))); // P2PKH
if (keys[0].IsCompressed()) {
Script p2wpkh = GetScriptForDestination(WitnessV0KeyHash(id));
output.scripts.emplace(ScriptID(p2wpkh), p2wpkh);
ret.emplace_back(p2wpkh);
ret.emplace_back(GetScriptForDestination(ScriptHash(p2wpkh))); // P2SH-P2WPKH
}
return ret;
}

public ComboDescriptor(std::unique_ptr<PubkeyProvider> prov) : base(Vector(prov), null, "combo") {}
public bool IsSingleType(){ return false; }
}
}
37 changes: 37 additions & 0 deletions NBitcoin/Scripting/ConstPubkeyProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
namespace NBitcoin.Scripting
{
/** An object representing a parsed constant public key in a descriptor. */
public class ConstPubkeyProvider : PubkeyProvider
{
private PubKey m_pubkey;

public ConstPubkeyProvider(uint exp_index, PubKey pubkey)
: base(exp_index)
{
m_pubkey = pubkey;
}

public bool GetPubKey(int pos, SigningProvider arg, PubKey key, KeyOriginInfo info, DescriptorCache read_cache = null, DescriptorCache write_cache = null)
{
key = m_pubkey;
info.path.clear();
KeyID keyid = m_pubkey.GetID();
std::copy(keyid.begin(), keyid.begin() + sizeof(info.fingerprint), info.fingerprint);
return true;
}
public bool IsRange() { return false; }
public uint GetSize() { return m_pubkey.size(); }
public string ToString() { return HexStr(m_pubkey); }
public bool ToPrivateString(SigningProvider arg, string ret)
{
Key key;
if (!arg.GetKey(m_pubkey.GetID(), key)) return false;
ret = EncodeSecret(key);
return true;
}
public bool GetPrivKey(int pos, SigningProvider arg, Key key)
{
return arg.GetKey(m_pubkey.GetID(), key);
}
};
}
76 changes: 76 additions & 0 deletions NBitcoin/Scripting/DescriptorCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using System.Collections.Generic;

namespace NBitcoin.Scripting
{
/** Cache for single descriptor's derived extended pubkeys */
public class DescriptorCache
{
/** Map key expression index . map of (key derivation index . xpub) */
private IDictionary<uint, ExtPubKeyMap> m_derived_xpubs;
/** Map key expression index . parent xpub */
private ExtPubKeyMap m_parent_xpubs;

/** Cache a parent xpub
*
* @param[in] key_exp_pos Position of the key expression within the descriptor
* @param[in] xpub The ExtPubKey to cache
*/
public void CacheParentExtPubKey(uint key_exp_pos, ExtPubKey xpub)
{
m_parent_xpubs[key_exp_pos] = xpub;
}

/** Retrieve a cached parent xpub
*
* @param[in] key_exp_pos Position of the key expression within the descriptor
* @param[in] xpub The ExtPubKey to get from cache
*/
public bool GetCachedParentExtPubKey(uint key_exp_pos, ExtPubKey xpub)
{
var it = m_parent_xpubs.find(key_exp_pos);
if (it == m_parent_xpubs.end()) return false;
xpub = it.second;
return true;
}

/** Cache an xpub derived at an index
*
* @param[in] key_exp_pos Position of the key expression within the descriptor
* @param[in] der_index Derivation index of the xpub
* @param[in] xpub The ExtPubKey to cache
*/
public void CacheDerivedExtPubKey(uint key_exp_pos, uint der_index, ExtPubKey xpub)
{
var xpubs = m_derived_xpubs[key_exp_pos];
xpubs[der_index] = xpub;
}

/** Retrieve a cached xpub derived at an index
*
* @param[in] key_exp_pos Position of the key expression within the descriptor
* @param[in] der_index Derivation index of the xpub
* @param[in] xpub The ExtPubKey to get from cache
*/
public bool GetCachedDerivedExtPubKey(uint key_exp_pos, uint der_index, ExtPubKey xpub)
{
var key_exp_it = m_derived_xpubs.find(key_exp_pos);
if (key_exp_it == m_derived_xpubs.end()) return false;
var der_it = key_exp_it.second.find(der_index);
if (der_it == key_exp_it.second.end()) return false;
xpub = der_it.second;
return true;
}

/** Retrieve all cached parent xpubs */
public ExtPubKeyMap GetCachedParentExtPubKeys()
{
return m_parent_xpubs;
}

/** Retrieve all cached derived xpubs */
public IDictionary<uint32_t, ExtPubKeyMap> GetCachedDerivedExtPubKeys()
{
return m_derived_xpubs;
}
};
}
Loading