Support bech32 addresses with versions other than 0

This commit is contained in:
Jean-Pierre Rupp 2020-12-10 19:21:57 +00:00
parent 6f120c7bda
commit ee44a7e5f7
No known key found for this signature in database
GPG Key ID: 93391726EAFA0C5D
7 changed files with 127 additions and 19 deletions

View File

@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## 0.18.0
### Added
- Support SegWit addresses with version other than 0.
## 0.17.6
### Added
- Serialize instances for `XPubKey` and `XPrvKey`.

View File

@ -4,10 +4,10 @@ cabal-version: 1.12
--
-- see: https://github.com/sol/hpack
--
-- hash: 80ca7faee9e1700b5927a262126a774fb45156219675eb255ba45e31aabcfe92
-- hash: 9861fe70166471990a6ee8fcf42174a9d40cda5b4e28119073d56a221e78fee3
name: haskoin-core
version: 0.17.6
version: 0.18.0
synopsis: Bitcoin & Bitcoin Cash library for Haskell
description: Please see the README on GitHub at <https://github.com/haskoin/haskoin-core#readme>
category: Bitcoin, Finance, Network

View File

@ -1,5 +1,5 @@
name: haskoin-core
version: 0.17.6
version: 0.18.0
synopsis: Bitcoin & Bitcoin Cash library for Haskell
description: Please see the README on GitHub at <https://github.com/haskoin/haskoin-core#readme>
category: Bitcoin, Finance, Network

View File

@ -64,6 +64,7 @@ import Data.Hashable
import Data.Maybe
import Data.Serialize as S
import Data.Text (Text)
import Data.Word (Word8)
import GHC.Generics (Generic)
import Haskoin.Address.Base58
import Haskoin.Address.Bech32
@ -96,6 +97,11 @@ data Address
{ getAddrHash256 :: !Hash256
-- ^ HASH256 hash of script
}
-- | other witness address
| WitnessAddress
{ getAddrVersion :: !Word8
, getAddrData :: !ByteString
}
deriving (Eq, Ord, Generic, Show, Read, Serialize, Hashable, NFData)
-- | 'Address' pays to a public key hash.
@ -118,6 +124,10 @@ isWitnessScriptAddress :: Address -> Bool
isWitnessScriptAddress WitnessScriptAddress {} = True
isWitnessScriptAddress _ = False
isWitnessAddress :: Address -> Bool
isWitnessAddress WitnessAddress {} = True
isWitnessAddress _ = False
addrToJSON :: Network -> Address -> Value
addrToJSON net a = toJSON (addrToText net a)
@ -151,6 +161,9 @@ addrToText net WitnessPubKeyAddress {getAddrHash160 = h} = do
addrToText net WitnessScriptAddress {getAddrHash256 = h} = do
hrp <- getBech32Prefix net
segwitEncode hrp 0 (B.unpack (S.encode h))
addrToText net WitnessAddress {getAddrVersion = v, getAddrData = d} = do
hrp <- getBech32Prefix net
segwitEncode hrp v (B.unpack d)
-- | Parse 'Base58', 'Bech32' or 'CashAddr' address, depending on network.
textToAddr :: Network -> Text -> Maybe Address
@ -169,11 +182,12 @@ bech32ToAddr :: Network -> Text -> Maybe Address
bech32ToAddr net txt = do
hrp <- getBech32Prefix net
(ver, bs) <- second B.pack <$> segwitDecode hrp txt
guard (ver == 0) -- We only support version 0 for now
case B.length bs of
20 -> WitnessPubKeyAddress <$> eitherToMaybe (S.decode bs)
32 -> WitnessScriptAddress <$> eitherToMaybe (S.decode bs)
_ -> Nothing
case ver of
0 -> case B.length bs of
20 -> WitnessPubKeyAddress <$> eitherToMaybe (S.decode bs)
32 -> WitnessScriptAddress <$> eitherToMaybe (S.decode bs)
_ -> Nothing
_ -> Just $ WitnessAddress ver bs
base58ToAddr :: Network -> Text -> Maybe Address
base58ToAddr net txt =
@ -250,10 +264,11 @@ payToNestedScriptAddress =
addressToOutput :: Address -> ScriptOutput
addressToOutput =
\case
(PubKeyAddress h) -> PayPKHash h
(ScriptAddress h) -> PayScriptHash h
(WitnessPubKeyAddress h) -> PayWitnessPKHash h
(WitnessScriptAddress h) -> PayWitnessScriptHash h
PubKeyAddress h -> PayPKHash h
ScriptAddress h -> PayScriptHash h
WitnessPubKeyAddress h -> PayWitnessPKHash h
WitnessScriptAddress h -> PayWitnessScriptHash h
WitnessAddress v d -> PayWitness v d
-- | Get output script AST for an 'Address'.
addressToScript :: Address -> Script
@ -277,11 +292,12 @@ scriptToAddressBS =
outputAddress :: ScriptOutput -> Maybe Address
outputAddress =
\case
(PayPKHash h) -> Just $ PubKeyAddress h
(PayScriptHash h) -> Just $ ScriptAddress h
(PayPK k) -> Just $ pubKeyAddr k
(PayWitnessPKHash h) -> Just $ WitnessPubKeyAddress h
(PayWitnessScriptHash h) -> Just $ WitnessScriptAddress h
PayPKHash h -> Just $ PubKeyAddress h
PayScriptHash h -> Just $ ScriptAddress h
PayPK k -> Just $ pubKeyAddr k
PayWitnessPKHash h -> Just $ WitnessPubKeyAddress h
PayWitnessScriptHash h -> Just $ WitnessScriptAddress h
PayWitness v d -> Just $ WitnessAddress v d
_ -> Nothing
-- | Infer the 'Address' of a 'ScriptInput'.

View File

@ -53,7 +53,9 @@ import qualified Data.ByteString as BS
import Data.Function (on)
import Data.Hashable
import Data.List (sortBy)
import Data.Maybe (fromJust, isJust)
import Data.Serialize as S
import Data.Word (Word8)
import GHC.Generics (Generic)
import Haskoin.Constants
import Haskoin.Crypto
@ -79,6 +81,10 @@ data ScriptOutput
| PayWitnessPKHash { getOutputHash :: !Hash160 }
-- | pay to witness script hash
| PayWitnessScriptHash { getScriptHash :: !Hash256 }
-- | another pay to witness address
| PayWitness { getWitnessVersion :: !Word8
, getWitnessData :: !ByteString
}
-- | provably unspendable data carrier
| DataCarrier { getOutputData :: !ByteString }
deriving (Eq, Show, Read, Generic, Hashable, NFData)
@ -124,6 +130,11 @@ isPayWitnessScriptHash :: ScriptOutput -> Bool
isPayWitnessScriptHash (PayWitnessScriptHash _) = True
isPayWitnessScriptHash _ = False
-- | Is script paying to a different type of witness address?
isPayWitness :: ScriptOutput -> Bool
isPayWitness (PayWitness _ _) = True
isPayWitness _ = False
-- | Is script a data carrier output?
isDataCarrier :: ScriptOutput -> Bool
isDataCarrier (DataCarrier _) = True
@ -145,11 +156,57 @@ decodeOutput s = case scriptOps s of
[OP_0, OP_PUSHDATA bs OPCODE]
| BS.length bs == 20 -> PayWitnessPKHash <$> S.decode bs
| BS.length bs == 32 -> PayWitnessScriptHash <$> S.decode bs
-- Other Witness
[ver, OP_PUSHDATA bs _]
| isJust (opWitnessVersion ver)
&& BS.length bs >= 2
&& BS.length bs <= 40 ->
Right $ PayWitness (fromJust (opWitnessVersion ver)) bs
-- Provably unspendable data carrier output
[OP_RETURN, OP_PUSHDATA bs _] -> Right $ DataCarrier bs
-- Pay to MultiSig Keys
_ -> matchPayMulSig s
witnessVersionOp :: Word8 -> Maybe ScriptOp
witnessVersionOp 0 = Just OP_0
witnessVersionOp 1 = Just OP_1
witnessVersionOp 2 = Just OP_2
witnessVersionOp 3 = Just OP_3
witnessVersionOp 4 = Just OP_4
witnessVersionOp 5 = Just OP_5
witnessVersionOp 6 = Just OP_6
witnessVersionOp 7 = Just OP_7
witnessVersionOp 8 = Just OP_8
witnessVersionOp 9 = Just OP_9
witnessVersionOp 10 = Just OP_10
witnessVersionOp 11 = Just OP_11
witnessVersionOp 12 = Just OP_12
witnessVersionOp 13 = Just OP_13
witnessVersionOp 14 = Just OP_14
witnessVersionOp 15 = Just OP_15
witnessVersionOp 16 = Just OP_16
opWitnessVersion :: ScriptOp -> Maybe Word8
opWitnessVersion OP_0 = Just 0
opWitnessVersion OP_1 = Just 1
opWitnessVersion OP_2 = Just 2
opWitnessVersion OP_3 = Just 3
opWitnessVersion OP_4 = Just 4
opWitnessVersion OP_5 = Just 5
opWitnessVersion OP_6 = Just 6
opWitnessVersion OP_7 = Just 7
opWitnessVersion OP_8 = Just 8
opWitnessVersion OP_9 = Just 9
opWitnessVersion OP_10 = Just 10
opWitnessVersion OP_11 = Just 11
opWitnessVersion OP_12 = Just 12
opWitnessVersion OP_13 = Just 13
opWitnessVersion OP_14 = Just 14
opWitnessVersion OP_15 = Just 15
opWitnessVersion OP_16 = Just 16
opWitnessVersion _ = Nothing
-- | Similar to 'decodeOutput' but decodes from a 'ByteString'.
decodeOutputBS :: ByteString -> Either String ScriptOutput
decodeOutputBS = decodeOutput <=< S.decode
@ -161,7 +218,11 @@ encodeOutput s = Script $ case s of
(PayPK k) -> [opPushData $ S.encode k, OP_CHECKSIG]
-- Pay to PubKey Hash Address
(PayPKHash h) ->
[ OP_DUP, OP_HASH160, opPushData $ S.encode h, OP_EQUALVERIFY, OP_CHECKSIG]
[ OP_DUP
, OP_HASH160
, opPushData $ S.encode h
, OP_EQUALVERIFY, OP_CHECKSIG
]
-- Pay to MultiSig Keys
(PayMulSig ps r)
| r <= length ps ->
@ -178,6 +239,11 @@ encodeOutput s = Script $ case s of
[ OP_0, opPushData $ S.encode h ]
(PayWitnessScriptHash h) ->
[ OP_0, opPushData $ S.encode h ]
(PayWitness v h) ->
[ case witnessVersionOp v of
Nothing -> error "encodeOutput: invalid witness version"
Just c -> c
, opPushData h ]
-- Provably unspendable output
(DataCarrier d) -> [OP_RETURN, opPushData d]

View File

@ -9,6 +9,7 @@ Portability : POSIX
-}
module Haskoin.Util.Arbitrary.Address where
import qualified Data.ByteString as B
import Haskoin.Address
import Haskoin.Constants
import Haskoin.Util.Arbitrary.Crypto
@ -26,6 +27,7 @@ arbitraryAddressAll =
, arbitraryScriptAddress
, arbitraryWitnessPubKeyAddress
, arbitraryWitnessScriptAddress
, arbitraryWitnessAddress
]
-- | Arbitrary valid combination of (Network, Address)
@ -51,3 +53,11 @@ arbitraryWitnessPubKeyAddress = WitnessPubKeyAddress <$> arbitraryHash160
-- | Arbitrary pay-to-witness script hash
arbitraryWitnessScriptAddress :: Gen Address
arbitraryWitnessScriptAddress = WitnessPubKeyAddress <$> arbitraryHash160
arbitraryWitnessAddress :: Gen Address
arbitraryWitnessAddress = do
ver <- choose (0, 16)
len <- choose (2, 40)
ws <- vectorOf len arbitrary
let bs = B.pack ws
return $ WitnessAddress ver bs

View File

@ -10,6 +10,7 @@ Portability : POSIX
module Haskoin.Util.Arbitrary.Script where
import Crypto.Secp256k1
import qualified Data.ByteString as B
import Data.Maybe
import Data.Word
import Haskoin.Address
@ -239,7 +240,10 @@ arbitraryScriptOutput net =
, arbitraryDCOutput
] ++
if getSegWit net
then [arbitraryWPKHashOutput, arbitraryWSHOutput]
then [ arbitraryWPKHashOutput
, arbitraryWSHOutput
, arbitraryWitOutput
]
else []
-- | Arbitrary 'ScriptOutput' of type 'PayPK', 'PayPKHash' or 'PayMS'
@ -268,6 +272,14 @@ arbitraryWPKHashOutput = PayWitnessPKHash <$> arbitraryHash160
arbitraryWSHOutput :: Gen ScriptOutput
arbitraryWSHOutput = PayWitnessScriptHash <$> arbitraryHash256
arbitraryWitOutput :: Gen ScriptOutput
arbitraryWitOutput = do
ver <- choose (0, 16)
len <- choose (2, 40)
ws <- vectorOf len arbitrary
let bs = B.pack ws
return $ PayWitness ver bs
-- | Arbitrary 'ScriptOutput' of type 'PayMS'.
arbitraryMSOutput :: Gen ScriptOutput
arbitraryMSOutput = do