diff --git a/src/Nerdbank.Zcash/PublicAPI.Unshipped.txt b/src/Nerdbank.Zcash/PublicAPI.Unshipped.txt index 9eb3f338..c6f8c259 100644 --- a/src/Nerdbank.Zcash/PublicAPI.Unshipped.txt +++ b/src/Nerdbank.Zcash/PublicAPI.Unshipped.txt @@ -421,8 +421,19 @@ Nerdbank.Zcash.SproutReceiver.Span.get -> System.ReadOnlySpan Nerdbank.Zcash.SproutReceiver.SproutReceiver() -> void Nerdbank.Zcash.SproutReceiver.SproutReceiver(System.ReadOnlySpan apk, System.ReadOnlySpan pkEnc) -> void Nerdbank.Zcash.TexAddress -Nerdbank.Zcash.TexAddress.TexAddress(in Nerdbank.Zcash.TransparentP2PKHReceiver receiver, Nerdbank.Zcash.ZcashNetwork network = Nerdbank.Zcash.ZcashNetwork.MainNet) -> void +Nerdbank.Zcash.TexAddress.TexAddress(in Nerdbank.Zcash.TexReceiver receiver, Nerdbank.Zcash.ZcashNetwork network = Nerdbank.Zcash.ZcashNetwork.MainNet) -> void Nerdbank.Zcash.TexAddress.TexAddress(Nerdbank.Zcash.TransparentP2PKHAddress! transparentAddress) -> void +Nerdbank.Zcash.TexReceiver +Nerdbank.Zcash.TexReceiver.Encode(System.Span buffer) -> int +Nerdbank.Zcash.TexReceiver.EncodingLength.get -> int +Nerdbank.Zcash.TexReceiver.Equals(Nerdbank.Zcash.TexReceiver other) -> bool +Nerdbank.Zcash.TexReceiver.Pool.get -> Nerdbank.Zcash.Pool +Nerdbank.Zcash.TexReceiver.Span.get -> System.ReadOnlySpan +Nerdbank.Zcash.TexReceiver.TexReceiver() -> void +Nerdbank.Zcash.TexReceiver.TexReceiver(Nerdbank.Zcash.Transparent.PublicKey! publicKey) -> void +Nerdbank.Zcash.TexReceiver.TexReceiver(Nerdbank.Zcash.Zip32HDWallet.Transparent.ExtendedViewingKey! publicKey) -> void +Nerdbank.Zcash.TexReceiver.TexReceiver(System.ReadOnlySpan p2pkh) -> void +Nerdbank.Zcash.TexReceiver.ValidatingKeyHash.get -> System.ReadOnlySpan Nerdbank.Zcash.Transaction Nerdbank.Zcash.Transaction.Change.get -> System.Collections.Immutable.ImmutableArray Nerdbank.Zcash.Transaction.ExpiredUnmined.get -> bool @@ -891,6 +902,8 @@ static Nerdbank.Zcash.Sapling.DiversifiableIncomingViewingKey.TryDecode(string! static Nerdbank.Zcash.Sapling.FullViewingKey.TryDecode(string! encoding, out Nerdbank.Cryptocurrencies.DecodeError? decodeError, out string? errorMessage, out Nerdbank.Zcash.Sapling.FullViewingKey? key) -> bool static Nerdbank.Zcash.Sapling.IncomingViewingKey.TryDecode(System.ReadOnlySpan encoding, out Nerdbank.Cryptocurrencies.DecodeError? decodeError, out string? errorMessage, out Nerdbank.Zcash.Sapling.IncomingViewingKey? key) -> bool static Nerdbank.Zcash.SaplingReceiver.UnifiedReceiverTypeCode.get -> byte +static Nerdbank.Zcash.TexReceiver.explicit operator Nerdbank.Zcash.TransparentP2PKHReceiver(in Nerdbank.Zcash.TexReceiver receiver) -> Nerdbank.Zcash.TransparentP2PKHReceiver +static Nerdbank.Zcash.TexReceiver.implicit operator Nerdbank.Zcash.TexReceiver(in Nerdbank.Zcash.TransparentP2PKHReceiver receiver) -> Nerdbank.Zcash.TexReceiver static Nerdbank.Zcash.Transaction.operator !=(Nerdbank.Zcash.Transaction? left, Nerdbank.Zcash.Transaction? right) -> bool static Nerdbank.Zcash.Transaction.operator ==(Nerdbank.Zcash.Transaction? left, Nerdbank.Zcash.Transaction? right) -> bool static Nerdbank.Zcash.Transaction.LineItem.operator !=(Nerdbank.Zcash.Transaction.LineItem left, Nerdbank.Zcash.Transaction.LineItem right) -> bool diff --git a/src/Nerdbank.Zcash/TexAddress.cs b/src/Nerdbank.Zcash/TexAddress.cs index a2540636..cc66dfe2 100644 --- a/src/Nerdbank.Zcash/TexAddress.cs +++ b/src/Nerdbank.Zcash/TexAddress.cs @@ -11,18 +11,18 @@ namespace Nerdbank.Zcash; /// /// TEX addresses are designed for use by exchanges that require transparently funded accounts for compliance purposes /// and so that they have an address to return rejected funds. -/// The matching receiver type for this address is . +/// The matching receiver type for this address is . /// This implements ZIP-320. /// public class TexAddress : TransparentAddress { [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly TransparentP2PKHReceiver receiver; + private readonly TexReceiver receiver; [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly ZcashNetwork network; - /// - public TexAddress(in TransparentP2PKHReceiver receiver, ZcashNetwork network = ZcashNetwork.MainNet) + /// + public TexAddress(in TexReceiver receiver, ZcashNetwork network = ZcashNetwork.MainNet) : base(CreateAddress(receiver, network)) { this.receiver = receiver; @@ -45,7 +45,7 @@ public TexAddress(TransparentP2PKHAddress transparentAddress) /// /// The encoded receiver. /// The network to which this address belongs. - internal TexAddress(string address, in TransparentP2PKHReceiver receiver, ZcashNetwork network) + internal TexAddress(string address, in TexReceiver receiver, ZcashNetwork network) : base(address) { this.receiver = receiver; @@ -65,7 +65,7 @@ internal TexAddress(string address, in TransparentP2PKHReceiver receiver, ZcashN internal override int ReceiverEncodingLength => this.receiver.EncodingLength; /// - public override TPoolReceiver? GetPoolReceiver() => AsReceiver(this.receiver); + public override TPoolReceiver? GetPoolReceiver() => AsReceiver(this.receiver); /// /// Checks whether a given string looks like a Zcash TEX address. @@ -107,7 +107,7 @@ internal static bool TryParse(string address, [NotNullWhen(false)] out DecodeErr return false; } - result = new TexAddress(address, new TransparentP2PKHReceiver(data), network.Value); + result = new TexAddress(address, new TexReceiver(data), network.Value); return true; } @@ -120,7 +120,7 @@ internal static bool TryParse(string address, [NotNullWhen(false)] out DecodeErr /// internal override int GetReceiverEncoding(Span output) => this.receiver.Encode(output); - private static string CreateAddress(in TransparentP2PKHReceiver receiver, ZcashNetwork network) + private static string CreateAddress(in TexReceiver receiver, ZcashNetwork network) { string hrp = network switch { diff --git a/src/Nerdbank.Zcash/TexReceiver.cs b/src/Nerdbank.Zcash/TexReceiver.cs new file mode 100644 index 00000000..94b79741 --- /dev/null +++ b/src/Nerdbank.Zcash/TexReceiver.cs @@ -0,0 +1,81 @@ +// Copyright (c) Andrew Arnott. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Nerdbank.Zcash; + +/// +/// A receiver that contains the cryptography parameters required to send Zcash to the pool +/// by way of a Pay to Public Key Hash method. +/// +/// +/// This receiver is used for to represent receivers that must only be sent transparent funds. +/// It is otherwise equivalent to . +/// +public unsafe struct TexReceiver : IPoolReceiver, IEquatable +{ + private readonly TransparentP2PKHReceiver p2pkhReceiver; + + /// + /// Initializes a new instance of the struct. + /// + /// The validating key hash. + /// Thrown when the arguments have an unexpected length. + public TexReceiver(ReadOnlySpan p2pkh) + { + this.p2pkhReceiver = new(p2pkh); + } + + /// + /// Initializes a new instance of the struct. + /// + /// The EC public key to create a receiver for. + public TexReceiver(Zip32HDWallet.Transparent.ExtendedViewingKey publicKey) + { + this.p2pkhReceiver = new(publicKey); + } + + /// + /// Initializes a new instance of the struct. + /// + /// The EC public key to create a receiver for. + public TexReceiver(Transparent.PublicKey publicKey) + { + this.p2pkhReceiver = new(publicKey); + } + + /// + public readonly Pool Pool => Pool.Transparent; + + /// + /// Gets the encoded representation of the entire receiver. + /// + [UnscopedRef] + public readonly ReadOnlySpan Span => this.ValidatingKeyHash; + + /// + public readonly int EncodingLength => this.Span.Length; + + /// + /// Gets the validating key hash. + /// + [UnscopedRef] + public readonly ReadOnlySpan ValidatingKeyHash => this.p2pkhReceiver.ValidatingKeyHash; + + /// + /// Converts a into a . + /// + /// The receiver to convert from. + public static implicit operator TexReceiver(in TransparentP2PKHReceiver receiver) => new(receiver.ValidatingKeyHash); + + /// + /// Converts a into a . + /// + /// The receiver to convert from. + public static explicit operator TransparentP2PKHReceiver(in TexReceiver receiver) => new(receiver.ValidatingKeyHash); + + /// + public int Encode(Span buffer) => this.Span.CopyToRetLength(buffer); + + /// + public bool Equals(TexReceiver other) => this.ValidatingKeyHash.SequenceEqual(other.ValidatingKeyHash); +} diff --git a/src/Nerdbank.Zcash/TransparentAddress.cs b/src/Nerdbank.Zcash/TransparentAddress.cs index eee88e45..7bb7c73e 100644 --- a/src/Nerdbank.Zcash/TransparentAddress.cs +++ b/src/Nerdbank.Zcash/TransparentAddress.cs @@ -6,12 +6,6 @@ namespace Nerdbank.Zcash; /// /// A transparent Zcash address. /// -/// -/// Not all transparent addresses carry the same semantics. -/// For example, care should be taken when extracting a -/// to check whether the is an instance of , -/// which should only receive funds from unshielded sources. -/// public abstract class TransparentAddress : ZcashAddress { /// diff --git a/test/Nerdbank.Zcash.Tests/TexAddressTests.cs b/test/Nerdbank.Zcash.Tests/TexAddressTests.cs index 87988de4..7dfdbcdc 100644 --- a/test/Nerdbank.Zcash.Tests/TexAddressTests.cs +++ b/test/Nerdbank.Zcash.Tests/TexAddressTests.cs @@ -15,6 +15,8 @@ public void SameReceiverAsTransparent() { var tex = (TexAddress)ZcashAddress.Decode("tex1s2rt77ggv6q989lr49rkgzmh5slsksa9khdgte"); var tAddr = (TransparentP2PKHAddress)ZcashAddress.Decode("t1VmmGiyjVNeCjxDZzg7vZmd99WyzVby9yC"); - Assert.Equal(tAddr.GetPoolReceiver(), tex.GetPoolReceiver()); + Assert.Equal( + tAddr.GetPoolReceiver()!.Value.ValidatingKeyHash, + tex.GetPoolReceiver()!.Value.ValidatingKeyHash); } } diff --git a/test/Nerdbank.Zcash.Tests/TexReceiverTests.cs b/test/Nerdbank.Zcash.Tests/TexReceiverTests.cs new file mode 100644 index 00000000..a8679207 --- /dev/null +++ b/test/Nerdbank.Zcash.Tests/TexReceiverTests.cs @@ -0,0 +1,47 @@ +// Copyright (c) Andrew Arnott. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +public class TexReceiverTests +{ + [Fact] + public void Ctor() + { + byte[] hash = new byte[20]; + hash[1] = 2; + TexReceiver receiver = new(hash); + Assert.Equal(hash, receiver.ValidatingKeyHash.ToArray()); + + // Verify that a copy of the data has been made. + hash[0] = 3; + Assert.Equal(0, receiver.ValidatingKeyHash[0]); + } + + [Fact] + public void Ctor_ArgValidation() + { + Assert.Throws(() => new TexReceiver(new byte[1])); + } + + [Fact] + public void Pool_Transparent() => Assert.Equal(Pool.Transparent, default(TexReceiver).Pool); + + [Fact] + public void TexToTransparentConversion() + { + Span p2pkh = stackalloc byte[20]; + Random.Shared.NextBytes(p2pkh); + TexReceiver texReceiver = new(p2pkh); + TransparentP2PKHReceiver transparentReceiver = (TransparentP2PKHReceiver)texReceiver; + Assert.Equal(texReceiver.ValidatingKeyHash, transparentReceiver.ValidatingKeyHash); + } + + [Fact] + public void TransparentToTexConversion() + { + Span p2pkh = stackalloc byte[20]; + Random.Shared.NextBytes(p2pkh); + TransparentP2PKHReceiver transparentReceiver = new(p2pkh); + TexReceiver texReceiver = transparentReceiver; + Assert.Equal(transparentReceiver.ValidatingKeyHash, texReceiver.ValidatingKeyHash); + } +} diff --git a/test/Nerdbank.Zcash.Tests/TransparentP2PKHReceiverTests.cs b/test/Nerdbank.Zcash.Tests/TransparentP2PKHReceiverTests.cs index 14091df4..f2ba2c50 100644 --- a/test/Nerdbank.Zcash.Tests/TransparentP2PKHReceiverTests.cs +++ b/test/Nerdbank.Zcash.Tests/TransparentP2PKHReceiverTests.cs @@ -23,7 +23,7 @@ public void Ctor_ArgValidation() } [Fact] - public void Pool_Orchard() => Assert.Equal(Pool.Transparent, default(TransparentP2PKHReceiver).Pool); + public void Pool_Transparent() => Assert.Equal(Pool.Transparent, default(TransparentP2PKHReceiver).Pool); [Fact] public void UnifiedReceiverTypeCode() => Assert.Equal(0x02, TransparentP2PKHReceiver.UnifiedReceiverTypeCode); diff --git a/test/Nerdbank.Zcash.Tests/TransparentP2SHReceiverTests.cs b/test/Nerdbank.Zcash.Tests/TransparentP2SHReceiverTests.cs index 4276e257..a6af9f2a 100644 --- a/test/Nerdbank.Zcash.Tests/TransparentP2SHReceiverTests.cs +++ b/test/Nerdbank.Zcash.Tests/TransparentP2SHReceiverTests.cs @@ -23,7 +23,7 @@ public void Ctor_ArgValidation() } [Fact] - public void Pool_Orchard() => Assert.Equal(Pool.Transparent, default(TransparentP2SHReceiver).Pool); + public void Pool_Transparent() => Assert.Equal(Pool.Transparent, default(TransparentP2SHReceiver).Pool); [Fact] public void UnifiedReceiverTypeCode() => Assert.Equal(0x01, TransparentP2SHReceiver.UnifiedReceiverTypeCode);