Skip to content

Commit f624ff4

Browse files
KukksNicolasDorier
authored andcommitted
Liquid updates (MetacoSA#759)
* Updated Liquid Support Set correct hrps for liquid networks Add Blech32 encoder for elements segwit blinded addresses Allow alt networks to have more bech32 encoders configured than btc Added additional RPC commands for elements Update WellKnownNode versions for elements and bitcoin Add additional serialization methods to BitcoinStream Adjust altcoin tests for elements differences Add support for dynamic federation mode blocks in elements Adjust Elements regtest genesis hash * test cleanup * adjust tests * remove tuple as CI does not understand it apparently * initialfreecoijns * add serialization to witscript * fix rpcs * add static parse for elements tx * blinded address fix * keep WitScript without stream logic * small test cleanup * remove extra elements test code * reintroduce static instance * switch tests back to btc * remove generics
1 parent 41384e3 commit f624ff4

25 files changed

+827
-141
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -192,3 +192,4 @@ project.lock.json
192192
/dotnet-tpl35/
193193
/dotnet-tpl35
194194
/NBitcoin.Bench/BenchmarkDotNet.Artifacts
195+
.idea

NBitcoin.Altcoins/Elements/BitcoinBlindedAddress.cs

+100-31
Original file line numberDiff line numberDiff line change
@@ -13,34 +13,78 @@ public BitcoinBlindedAddress(string base58, Network network)
1313
: base(base58, network)
1414
{
1515
var prefix = network.GetVersionBytes(Base58Type.BLINDED_ADDRESS, true);
16-
var vchData = Encoders.Base58Check.DecodeData(base58);
1716
var version = network.GetVersionBytes(Base58Type.PUBKEY_ADDRESS, false);
18-
bool p2pkh = true;
19-
if (version == null || !StartWith(prefix.Length, vchData, version))
20-
{
21-
p2pkh = false;
22-
version = network.GetVersionBytes(Base58Type.SCRIPT_ADDRESS, false);
23-
if (version == null || !StartWith(prefix.Length, vchData, version))
24-
{
25-
throw new FormatException("Invalid Bitcoin Blinded Address");
26-
}
27-
}
17+
var blech32 = network.GetBech32Encoder(Bech32Type.BLINDED_ADDRESS, false);
2818

29-
if (vchData.Length != prefix.Length + version.Length + 33 + 20)
30-
throw new FormatException("Invalid Bitcoin Blinded Address");
31-
var blinding = vchData.SafeSubarray(prefix.Length + version.Length, 33);
32-
if (PubKey.Check(blinding, true))
19+
if (blech32 != null && NBitcoin.DataEncoders.Encoders.ASCII
20+
.DecodeData(base58.Substring(0, blech32.HumanReadablePart.Length))
21+
.SequenceEqual(blech32.HumanReadablePart))
3322
{
34-
_BlindingKey = new PubKey(blinding);
23+
var vchData = blech32.Decode(base58, out var witnessVerion);
24+
bool p2pkh = !(version == null || !StartWith(prefix.Length, vchData, version));
25+
var script = false;
26+
var blinding = vchData.SafeSubarray(0, 33);
27+
byte[] hash;
28+
if (vchData.Length == 53)
29+
{
30+
31+
hash = vchData.SafeSubarray(version.Length + 32, 20);
32+
}
33+
else
34+
{
35+
hash = vchData.SafeSubarray(version.Length + 32, vchData.Length - version.Length - 32);
36+
script = true;
37+
}
38+
if (PubKey.Check(blinding, true))
39+
{
40+
_BlindingKey = new PubKey(blinding);
41+
if (witnessVerion == 0)
42+
{
43+
_UnblindedAddress =script? (BitcoinAddress) new BitcoinWitScriptAddress(new WitScriptId(hash), network): new BitcoinWitPubKeyAddress(new WitKeyId(hash), network);
44+
}
45+
else if (witnessVerion > 16 || hash.Length < 2 || hash.Length > 40)
46+
{
47+
throw new FormatException("Invalid Bitcoin Blinded Address");
48+
}
49+
}
50+
else
51+
{
52+
throw new FormatException("Invalid Bitcoin Blinded Address");
53+
}
3554

36-
var hash = vchData.SafeSubarray(prefix.Length + version.Length + 33, 20);
37-
_UnblindedAddress =
38-
p2pkh ? (BitcoinAddress)new BitcoinPubKeyAddress(new KeyId(hash), network)
39-
: new BitcoinScriptAddress(new ScriptId(hash), network);
4055
}
4156
else
4257
{
43-
throw new FormatException("Invalid Bitcoin Blinded Address");
58+
59+
var vchData = NBitcoin.DataEncoders.Encoders.Base58Check.DecodeData(base58);
60+
bool p2pkh = true;
61+
if (version == null || !StartWith(prefix.Length, vchData, version))
62+
{
63+
p2pkh = false;
64+
version = network.GetVersionBytes(Base58Type.SCRIPT_ADDRESS, false);
65+
if (version == null || !StartWith(prefix.Length, vchData, version))
66+
{
67+
throw new FormatException("Invalid Bitcoin Blinded Address");
68+
}
69+
}
70+
71+
if (vchData.Length != prefix.Length + version.Length + 33 + 20)
72+
throw new FormatException("Invalid Bitcoin Blinded Address");
73+
var blinding = vchData.SafeSubarray(prefix.Length + version.Length, 33);
74+
if (PubKey.Check(blinding, true))
75+
{
76+
_BlindingKey = new PubKey(blinding);
77+
78+
var hash = vchData.SafeSubarray(prefix.Length + version.Length + 33, 20);
79+
_UnblindedAddress =
80+
p2pkh
81+
? (BitcoinAddress) new BitcoinPubKeyAddress(new KeyId(hash), network)
82+
: new BitcoinScriptAddress(new ScriptId(hash), network);
83+
}
84+
else
85+
{
86+
throw new FormatException("Invalid Bitcoin Blinded Address");
87+
}
4488
}
4589
}
4690

@@ -60,15 +104,40 @@ private static string GetBase58(PubKey blindingKey, BitcoinAddress address)
60104

61105
if (address is BitcoinBlindedAddress ba)
62106
address = ba.UnblindedAddress;
63-
if(!(address is IBase58Data))
64-
throw new ArgumentException("Unsupported address");
65-
66-
var network = address.Network;
67-
var keyId = address.ScriptPubKey.GetDestination();
68-
if (keyId == null)
69-
throw new ArgumentException("The passed address can't be reduced to a hash");
70-
var bytes = address.Network.GetVersionBytes(Base58Type.BLINDED_ADDRESS, true).Concat(network.GetVersionBytes(((IBase58Data)address).Type, true), blindingKey.ToBytes(), keyId.ToBytes());
71-
return Encoders.Base58Check.EncodeData(bytes);
107+
if (!(address is IBase58Data))
108+
{
109+
byte witVer;
110+
byte[] witProg;
111+
var blech32Encoder = address.Network.GetBech32Encoder(Bech32Type.BLINDED_ADDRESS, true);
112+
Bech32Encoder bech32Encoder = null;
113+
switch (address)
114+
{
115+
case BitcoinWitPubKeyAddress _:
116+
bech32Encoder= address.Network.GetBech32Encoder(Bech32Type.WITNESS_PUBKEY_ADDRESS, true);
117+
break;
118+
case BitcoinWitScriptAddress _:
119+
bech32Encoder= address.Network.GetBech32Encoder(Bech32Type.WITNESS_SCRIPT_ADDRESS, true);
120+
break;
121+
default:
122+
throw new ArgumentException($"no bech32 encoder for {address.GetType()} found");
123+
}
124+
125+
witProg = bech32Encoder.Decode(address.ToString(), out witVer);
126+
return blech32Encoder.Encode(witVer, blindingKey.ToBytes().Concat(witProg));
127+
}
128+
else
129+
{
130+
131+
132+
var network = address.Network;
133+
var keyId = address.ScriptPubKey.GetDestination();
134+
if (keyId == null)
135+
throw new ArgumentException("The passed address can't be reduced to a hash");
136+
var bytes = address.Network.GetVersionBytes(Base58Type.BLINDED_ADDRESS, true).Concat(
137+
network.GetVersionBytes(((IBase58Data) address).Type, true), blindingKey.ToBytes(),
138+
keyId.ToBytes());
139+
return NBitcoin.DataEncoders.Encoders.Base58Check.EncodeData(bytes);
140+
}
72141
}
73142

74143
private static bool StartWith(int aoffset, byte[] a, byte[] b)
@@ -106,4 +175,4 @@ protected override Script GeneratePaymentScript()
106175
return _UnblindedAddress.ScriptPubKey;
107176
}
108177
}
109-
}
178+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using NBitcoin.DataEncoders;
5+
6+
namespace NBitcoin.Altcoins.Elements
7+
{
8+
public class Blech32Encoder : Bech32Encoder
9+
{
10+
protected static readonly ulong[] Generator = { 0x7d52fba40bd886, 0x5e8dbf1a03950c, 0x1c3a3c74072a18, 0x385d72fa0e5139, 0x7093e5a608865b };
11+
12+
private static ulong Polymod(byte[] values)
13+
{
14+
ulong chk = 1;
15+
foreach (var value in values)
16+
{
17+
var top = chk >> 55;
18+
chk = value ^ ((chk & 0x7fffffffffffff) <<
19+
5);
20+
foreach (var i in Enumerable.Range(0, 5))
21+
{
22+
chk ^= ((top >> i) & 1) == 1 ? Generator[i] : 0;
23+
}
24+
}
25+
return chk;
26+
}
27+
28+
protected override bool VerifyChecksum(byte[] data, int bechStringLen, out int[] errorPosition)
29+
{
30+
var values = _HrpExpand.Concat(data);
31+
errorPosition = new int[0];
32+
return Polymod(values) == 1;
33+
}
34+
35+
private byte[] CreateChecksum(byte[] data, int offset, int count)
36+
{
37+
var values = new byte[_HrpExpand.Length + count + 12];
38+
var valuesOffset = 0;
39+
Array.Copy(_HrpExpand, 0, values, valuesOffset, _HrpExpand.Length);
40+
valuesOffset += _HrpExpand.Length;
41+
Array.Copy(data, offset, values, valuesOffset, count);
42+
valuesOffset += count;
43+
var polymod = Polymod(values) ^ 1;
44+
var ret = new byte[12];
45+
foreach (var i in Enumerable.Range(0, 12))
46+
{
47+
ret[i] = (byte)((polymod >> 5 * (11 - i)) & 31);
48+
}
49+
return ret;
50+
}
51+
52+
53+
public override string EncodeData(byte[] data, int offset, int count)
54+
{
55+
var combined = new byte[_Hrp.Length + 1 + count + 12];
56+
int combinedOffset = 0;
57+
Array.Copy(_Hrp, 0, combined, 0, _Hrp.Length);
58+
combinedOffset += _Hrp.Length;
59+
combined[combinedOffset] = 49;
60+
combinedOffset++;
61+
Array.Copy(data, offset, combined, combinedOffset, count);
62+
combinedOffset += count;
63+
var checkSum = CreateChecksum(data, offset, count);
64+
Array.Copy(checkSum, 0, combined, combinedOffset, 12);
65+
for (int i = 0; i < count + 12; i++)
66+
{
67+
combined[_Hrp.Length + 1 + i] = Byteset[combined[_Hrp.Length + 1 + i]];
68+
}
69+
return DataEncoders.Encoders.ASCII.EncodeData(combined);
70+
}
71+
72+
public new static Blech32Encoder ExtractEncoderFromString(string test)
73+
{
74+
var i = test.IndexOf('1');
75+
if (i == -1)
76+
throw new FormatException("Invalid Blech32 string");
77+
return Encoders.Blech32(test.Substring(0, i));
78+
}
79+
80+
protected override void CheckCase(string hrp)
81+
{
82+
if (hrp.ToLowerInvariant().Equals(hrp))
83+
return;
84+
if (hrp.ToUpperInvariant().Equals(hrp))
85+
return;
86+
throw new FormatException("Invalid blech32 string, mixed case detected");
87+
}
88+
89+
protected override byte[] DecodeDataCore(string encoded)
90+
{
91+
if (encoded == null)
92+
throw new ArgumentNullException(nameof(encoded));
93+
CheckCase(encoded);
94+
var buffer = DataEncoders.Encoders.ASCII.DecodeData(encoded);
95+
if (buffer.Any(b => b < 33 || b > 126))
96+
{
97+
throw new FormatException("bech chars are out of range");
98+
}
99+
encoded = encoded.ToLowerInvariant();
100+
buffer = DataEncoders.Encoders.ASCII.DecodeData(encoded);
101+
var pos = encoded.LastIndexOf("1", StringComparison.OrdinalIgnoreCase);
102+
if (encoded.Length > 1000 || pos == -1 || pos == 0 || pos + 13 > encoded.Length)
103+
{ // ELEMENTS: 90->1000, 7->13
104+
105+
throw new FormatException("blech missing separator, separator misplaced or too long input");
106+
}
107+
if (buffer.Skip(pos + 1).Any(x => !Byteset.Contains(x)))
108+
{
109+
throw new FormatException("bech chars are out of range");
110+
}
111+
112+
buffer = DataEncoders.Encoders.ASCII.DecodeData(encoded);
113+
var hrp = DataEncoders.Encoders.ASCII.DecodeData(encoded.Substring(0, pos));
114+
if (!hrp.SequenceEqual(_Hrp))
115+
{
116+
throw new FormatException("Mismatching human readable part");
117+
}
118+
var data = new byte[encoded.Length - pos - 1];
119+
for (int j = 0, i = pos + 1; i < encoded.Length; i++, j++)
120+
{
121+
data[j] = (byte)Array.IndexOf(Byteset, buffer[i]);
122+
}
123+
124+
if (!VerifyChecksum(data, encoded.Length, out var _))
125+
{
126+
throw new FormatException("Error while verifying Blech32 checksum");
127+
}
128+
return data.Take(data.Length - 12).ToArray();
129+
}
130+
131+
protected override byte[] ConvertBits(IEnumerable<byte> data, int fromBits, int toBits, bool pad = true)
132+
{
133+
var acc = 0;
134+
var bits = 0;
135+
var maxv = (1 << toBits) - 1;
136+
var ret = new List<byte>();
137+
foreach (var value in data)
138+
{
139+
if ((value >> fromBits) > 0)
140+
throw new FormatException("Invalid Blech32 string");
141+
acc = (acc << fromBits) | value;
142+
bits += fromBits;
143+
while (bits >= toBits)
144+
{
145+
bits -= toBits;
146+
ret.Add((byte)((acc >> bits) & maxv));
147+
}
148+
}
149+
if (pad)
150+
{
151+
if (bits > 0)
152+
{
153+
ret.Add((byte)((acc << (toBits - bits)) & maxv));
154+
}
155+
}
156+
else if (bits >= fromBits || (byte)(((acc << (toBits - bits)) & maxv)) != 0)
157+
{
158+
throw new FormatException("Invalid Blech32 string");
159+
}
160+
return ret.ToArray();
161+
}
162+
163+
public override byte[] Decode(string addr, out byte witnessVerion)
164+
{
165+
if (addr == null)
166+
throw new ArgumentNullException(nameof(addr));
167+
CheckCase(addr);
168+
var data = DecodeDataCore(addr);
169+
witnessVerion = data[0];
170+
171+
var decoded = ConvertBits(data.Skip(1), 5, 8, false);
172+
if (decoded.Length < 34)
173+
throw new FormatException("Invalid decoded data length");
174+
return decoded;
175+
}
176+
177+
internal Blech32Encoder(string hrp) : base(hrp)
178+
{
179+
}
180+
181+
public Blech32Encoder(byte[] hrp) : base(hrp)
182+
{
183+
}
184+
}
185+
}

0 commit comments

Comments
 (0)