diff --git a/confidential/confidential.go b/confidential/confidential.go index be681cd..be140f0 100644 --- a/confidential/confidential.go +++ b/confidential/confidential.go @@ -1,24 +1,177 @@ package confidential import ( - "bytes" "crypto/sha256" - "encoding/binary" "errors" - "github.com/vulpemventures/go-elements/internal/bufferutil" + "github.com/vulpemventures/go-elements/transaction" "github.com/vulpemventures/go-secp256k1-zkp" ) -const ( - ElementsUnconfidentialValueLength = 9 -) - -//NonceHash method generates hashed secret based on ecdh +// NonceHash method generates hashed secret based on ecdh. func NonceHash(pubKey, privKey []byte) ( result [32]byte, err error, ) { + return nonceHash(pubKey, privKey) +} + +// UnblindOutputResult is the type returned by the functions that unblind tx +// outs. It contains the unblinded asset and value and also the respective +// blinding factors. +type UnblindOutputResult struct { + Value uint64 + Asset []byte + ValueBlindingFactor []byte + AssetBlindingFactor []byte +} + +// UnblindOutputWithKey method unblinds a confidential transaction output with +// the given blinding private key. +func UnblindOutputWithKey( + out *transaction.TxOutput, + blindKey []byte, +) (*UnblindOutputResult, error) { + if !out.IsConfidential() { + return nil, nil + } + + nonce, err := NonceHash(out.Nonce, blindKey) + if err != nil { + return nil, err + } + return unblindOutput(out, nonce) +} + +// UnblindOutputWithNonce method unblinds a confidential transaction output with +// the given ecdh nonce calculated for example with the above NonceHash func. +func UnblindOutputWithNonce( + out *transaction.TxOutput, + nonce []byte, +) (*UnblindOutputResult, error) { + if !out.IsConfidential() { + return nil, nil + } + + var nonce32 [32]byte + copy(nonce32[:], nonce) + return unblindOutput(out, nonce32) +} + +type UnblindIssuanceResult struct { + Asset *UnblindOutputResult + Token *UnblindOutputResult +} + +func UnblindIssuance( + in *transaction.TxInput, + blindKeys [][]byte, +) (*UnblindIssuanceResult, error) { + return unblindIssuance(in, blindKeys) +} + +// FinalValueBlindingFactorArgs is the type provided to the function that +// calculates the blinder of the last output of a tx. +type FinalValueBlindingFactorArgs struct { + InValues []uint64 + OutValues []uint64 + InGenerators [][]byte + OutGenerators [][]byte + InFactors [][]byte + OutFactors [][]byte +} + +// FinalValueBlindingFactor method calculates the blinder as the sum of all +// previous blinders of a tx. +func FinalValueBlindingFactor(args FinalValueBlindingFactorArgs) ( + [32]byte, error, +) { + return finalValueBlindingFactor(args) +} + +// AssetCommitment method generates asset commitment +func AssetCommitment(asset, factor []byte) ([]byte, error) { + return assetCommitment(asset, factor) +} + +// ValueCommitment method generates value commitment +func ValueCommitment(value uint64, generator, factor []byte) ([]byte, error) { + return valueCommitment(value, generator, factor) +} + +type RangeProofArgs struct { + Value uint64 + Nonce [32]byte + Asset []byte + AssetBlindingFactor []byte + ValueBlindFactor [32]byte + ValueCommit []byte + ScriptPubkey []byte + MinValue uint64 + Exp int + MinBits int +} + +func (a RangeProofArgs) minValue() uint64 { + if a.MinValue <= 0 { + return 1 + } + return a.MinValue +} + +func (a RangeProofArgs) exp() int { + if a.Exp < -1 || a.Exp > 18 { + return 0 + } + return a.Exp +} + +func (a RangeProofArgs) minBits() int { + if a.MinBits <= 0 { + return 36 + } + return a.MinBits +} + +// RangeProof method calculates range proof +func RangeProof(args RangeProofArgs) ([]byte, error) { + return rangeProof(args) +} + +type SurjectionProofArgs struct { + OutputAsset []byte + OutputAssetBlindingFactor []byte + InputAssets [][]byte + InputAssetBlindingFactors [][]byte + Seed []byte +} + +func (a SurjectionProofArgs) nInputsToUse() int { + if len(a.InputAssets) >= 3 { + return 3 + } + return len(a.InputAssets) +} + +//SurjectionProof method generates surjection proof +func SurjectionProof(args SurjectionProofArgs) ([]byte, bool) { + return surjectionProof(args) +} + +type VerifySurjectionProofArgs struct { + InputAssets [][]byte + InputAssetBlindingFactors [][]byte + OutputAsset []byte + OutputAssetBlindingFactor []byte + Proof []byte +} + +// VerifySurjectionProof method verifies the validity of a surjection proof +func VerifySurjectionProof(args VerifySurjectionProofArgs) bool { + return verifySurjectionProof(args) +} + +func nonceHash(pubKey, privKey []byte) (result [32]byte, err error) { ctx, _ := secp256k1.ContextCreate(secp256k1.ContextBoth) defer secp256k1.ContextDestroy(ctx) @@ -36,32 +189,24 @@ func NonceHash(pubKey, privKey []byte) ( return } -type UnblindOutputArg struct { - Nonce [32]byte - Rangeproof []byte - ValueCommitment []byte - AssetCommitment []byte - ScriptPubkey []byte -} - -type UnblindOutputResult struct { - Value uint64 - Asset []byte - ValueBlindingFactor []byte - AssetBlindingFactor []byte -} - -//UnblindOutput method unblinds confidential transaction output -func UnblindOutput(input UnblindOutputArg) (*UnblindOutputResult, error) { +func unblindOutput( + out *transaction.TxOutput, + nonce [32]byte, +) (*UnblindOutputResult, error) { ctx, _ := secp256k1.ContextCreate(secp256k1.ContextBoth) defer secp256k1.ContextDestroy(ctx) - valueCommit, err := secp256k1.CommitmentParse(ctx, input.ValueCommitment) + valueCommit, err := secp256k1.CommitmentParse(ctx, out.Value) if err != nil { return nil, err } - gen, err := secp256k1.GeneratorFromBytes(input.AssetCommitment) + var gen *secp256k1.Generator + if len(out.Asset) == 33 { + gen, err = secp256k1.GeneratorFromBytes(out.Asset) + } else { + gen, err = secp256k1.GeneratorGenerate(ctx, out.Asset) + } if err != nil { return nil, err } @@ -69,9 +214,9 @@ func UnblindOutput(input UnblindOutputArg) (*UnblindOutputResult, error) { rewind, value, _, _, message, err := secp256k1.RangeProofRewind( ctx, valueCommit, - input.Rangeproof, - input.Nonce, - input.ScriptPubkey, + out.RangeProof, + nonce, + out.Script, gen, ) if err != nil { @@ -86,201 +231,186 @@ func UnblindOutput(input UnblindOutputArg) (*UnblindOutputResult, error) { }, nil } -type FinalValueBlindingFactorArg struct { - InValues []uint64 - OutValues []uint64 - InGenerators [][]byte - OutGenerators [][]byte - InFactors [][]byte - OutFactors [][]byte +func unblindIssuance( + in *transaction.TxInput, + blindKeys [][]byte, +) (*UnblindIssuanceResult, error) { + if len(blindKeys) <= 1 { + return nil, errors.New("missing asset blind private key") + } + if in.Issuance == nil { + return nil, errors.New("missing input issuance") + } + if len(in.IssuanceRangeProof) <= 0 { + return nil, errors.New("missing asset range proof") + } + + if len(in.Issuance.TokenAmount) > 0 { + if len(in.InflationRangeProof) <= 0 { + return nil, errors.New("missing token range proof") + } + if len(blindKeys) < 1 { + return nil, errors.New("missing token blind private key") + } + } + + asset, err := calcAssetHash(in) + if err != nil { + return nil, err + } + + outs := []*transaction.TxOutput{ + &transaction.TxOutput{ + Asset: asset, + Value: in.Issuance.AssetAmount, + RangeProof: in.IssuanceRangeProof, + Script: make([]byte, 0), + }, + } + if len(in.Issuance.TokenAmount) > 0 { + token, err := calcTokenHash(in) + if err != nil { + return nil, err + } + + outs = append(outs, &transaction.TxOutput{ + Asset: token, + Value: in.Issuance.TokenAmount, + RangeProof: in.InflationRangeProof, + Script: make([]byte, 0), + }) + } + + res := &UnblindIssuanceResult{} + for i, out := range outs { + var nonce [32]byte + copy(nonce[:], blindKeys[i]) + unblinded, err := unblindOutput(out, nonce) + if err != nil { + return nil, err + } + if i == 0 { + res.Asset = unblinded + res.Asset.Asset = out.Asset + res.Asset.AssetBlindingFactor = make([]byte, 32) + } else { + res.Token = unblinded + res.Token.Asset = out.Asset + res.Token.AssetBlindingFactor = make([]byte, 32) + } + } + return res, nil } -//FinalValueBlindingFactor method generates blind sum -func FinalValueBlindingFactor(input FinalValueBlindingFactorArg) ( - [32]byte, - error, +func finalValueBlindingFactor(args FinalValueBlindingFactorArgs) ( + [32]byte, error, ) { ctx, _ := secp256k1.ContextCreate(secp256k1.ContextBoth) defer secp256k1.ContextDestroy(ctx) - values := append(input.InValues, input.OutValues...) + values := append(args.InValues, args.OutValues...) generatorBlind := make([][]byte, 0) - generatorBlind = append(generatorBlind, input.InGenerators...) - generatorBlind = append(generatorBlind, input.OutGenerators...) + generatorBlind = append(generatorBlind, args.InGenerators...) + generatorBlind = append(generatorBlind, args.OutGenerators...) blindingFactor := make([][]byte, 0) - blindingFactor = append(blindingFactor, input.InFactors...) - blindingFactor = append(blindingFactor, input.OutFactors...) + blindingFactor = append(blindingFactor, args.InFactors...) + blindingFactor = append(blindingFactor, args.OutFactors...) return secp256k1.BlindGeneratorBlindSum( ctx, values, generatorBlind, blindingFactor, - len(input.InValues), + len(args.InValues), ) } -//AssetCommitment method generates asset commitment -func AssetCommitment(asset []byte, factor []byte) (result [33]byte, err error) { - ctx, _ := secp256k1.ContextCreate(secp256k1.ContextBoth) - defer secp256k1.ContextDestroy(ctx) - - generator, err := secp256k1.GeneratorGenerateBlinded(ctx, asset, factor) +func assetCommitment(asset []byte, factor []byte) ([]byte, error) { + generator, err := outAssetGenerator(asset, factor) if err != nil { - return + return nil, err } - - result = generator.Bytes() - - return + assetCommitment := generator.Bytes() + return assetCommitment[:], nil } -//ValueCommitment method generates value commitment -func ValueCommitment(value uint64, generator []byte, factor []byte) ( - result [33]byte, - err error, -) { +func valueCommitment(value uint64, generator, factor []byte) ([]byte, error) { ctx, _ := secp256k1.ContextCreate(secp256k1.ContextBoth) defer secp256k1.ContextDestroy(ctx) gen, err := secp256k1.GeneratorParse(ctx, generator) if err != nil { - return + return nil, err } commit, err := secp256k1.Commit(ctx, factor, value, gen) if err != nil { - return + return nil, err } - result = commit.Bytes() - return + valueCommitment := commit.Bytes() + return valueCommitment[:], nil } -type RangeProofArg struct { - Value uint64 - Nonce [32]byte - Asset []byte - AssetBlindingFactor []byte - ValueBlindFactor [32]byte - ValueCommit []byte - ScriptPubkey []byte - MinValue uint64 - Exp int - MinBits int -} - -//RangeProof method calculates range proof -func RangeProof(input RangeProofArg) ([]byte, error) { +func rangeProof(args RangeProofArgs) ([]byte, error) { ctx, _ := secp256k1.ContextCreate(secp256k1.ContextBoth) defer secp256k1.ContextDestroy(ctx) - generator, err := secp256k1.GeneratorGenerateBlinded( - ctx, - input.Asset, - input.AssetBlindingFactor, - ) + generator, err := outAssetGenerator(args.Asset, args.AssetBlindingFactor) if err != nil { return nil, err } - message := append(input.Asset, input.AssetBlindingFactor...) + message := append(args.Asset, args.AssetBlindingFactor...) - commit, err := secp256k1.CommitmentParse(ctx, input.ValueCommit) + commit, err := secp256k1.CommitmentParse(ctx, args.ValueCommit) if err != nil { return nil, err } - var mv uint64 - if input.MinValue > 0 { - mv = input.MinValue - } else { - mv = 1 - } - - var e int - if input.MinValue > 0 { - e = input.Exp - } else { - e = 1 - } - - var mb int - if input.MinBits > 0 { - mb = input.MinBits - } else { - mb = 36 - } - return secp256k1.RangeProofSign( ctx, - mv, + args.minValue(), commit, - input.ValueBlindFactor, - input.Nonce, - e, - mb, - input.Value, + args.ValueBlindFactor, + args.Nonce, + args.exp(), + args.minBits(), + args.Value, message, - input.ScriptPubkey, + args.ScriptPubkey, generator, ) } -type SurjectionProofArg struct { - OutputAsset []byte - OutputAssetBlindingFactor []byte - InputAssets [][]byte - InputAssetBlindingFactors [][]byte - Seed []byte -} - -//SurjectionProof method generates surjection proof -func SurjectionProof(input SurjectionProofArg) ([]byte, bool) { +func surjectionProof(args SurjectionProofArgs) ([]byte, bool) { ctx, _ := secp256k1.ContextCreate(secp256k1.ContextBoth) defer secp256k1.ContextDestroy(ctx) - outputGenerator, err := secp256k1.GeneratorGenerateBlinded( - ctx, - input.OutputAsset, - input.OutputAssetBlindingFactor, + inputGenerators, err := inAssetGenerators( + args.InputAssets, + args.InputAssetBlindingFactors, ) if err != nil { return nil, false } - inputGenerators := make([]secp256k1.Generator, 0) - for i, v := range input.InputAssets { - gen, err := secp256k1.GeneratorGenerateBlinded( - ctx, - v, - input.InputAssetBlindingFactors[i], - ) - if err != nil { - return nil, false - } - inputGenerators = append(inputGenerators, *gen) - } - - fixedInputTags := make([]secp256k1.FixedAssetTag, 0) - for _, inTag := range input.InputAssets { - fixedAssetTag, err := secp256k1.FixedAssetTagParse(inTag) - if err != nil { - return nil, false - } - fixedInputTags = append(fixedInputTags, *fixedAssetTag) + fixedInputTags, err := inFixedTags(args.InputAssets) + if err != nil { + return nil, false } - var nInputsToUse int - if len(input.InputAssets) > 3 { - nInputsToUse = 3 - } else { - nInputsToUse = len(input.InputAssets) + fixedOutputTag, err := outFixedTag(args.OutputAsset) + if err != nil { + return nil, false } - fixedOutputTag, err := secp256k1.FixedAssetTagParse(input.OutputAsset) + outputGenerator, err := outAssetGenerator( + args.OutputAsset, + args.OutputAssetBlindingFactor, + ) if err != nil { return nil, false } @@ -289,10 +419,10 @@ func SurjectionProof(input SurjectionProofArg) ([]byte, bool) { proof, inputIndex, err := secp256k1.SurjectionProofInitialize( ctx, fixedInputTags, - nInputsToUse, + args.nInputsToUse(), *fixedOutputTag, maxIterations, - input.Seed, + args.Seed, ) if err != nil { return nil, false @@ -304,8 +434,8 @@ func SurjectionProof(input SurjectionProofArg) ([]byte, bool) { inputGenerators, *outputGenerator, inputIndex, - input.InputAssetBlindingFactors[inputIndex], - input.OutputAssetBlindingFactor, + args.InputAssetBlindingFactors[inputIndex], + args.OutputAssetBlindingFactor, ) if err != nil { return nil, false @@ -323,48 +453,114 @@ func SurjectionProof(input SurjectionProofArg) ([]byte, bool) { return proof.Bytes(), true } -//SatoshiToElementsValue method converts Satoshi value to Elements value -func SatoshiToElementsValue(val uint64) ( - result [ElementsUnconfidentialValueLength]byte, - err error, -) { - unconfPrefix := byte(1) - b := bytes.NewBuffer([]byte{}) - if err = bufferutil.BinarySerializer.PutUint64( - b, - binary.LittleEndian, - val, - ); err != nil { - return +func verifySurjectionProof(args VerifySurjectionProofArgs) bool { + ctx, _ := secp256k1.ContextCreate(secp256k1.ContextBoth) + defer secp256k1.ContextDestroy(ctx) + + inGenerators, err := inAssetGenerators( + args.InputAssets, + args.InputAssetBlindingFactors, + ) + if err != nil { + return false } - copy( - result[:], - append([]byte{unconfPrefix}, bufferutil.ReverseBytes(b.Bytes())...), + + outGenerator, err := outAssetGenerator( + args.OutputAsset, + args.OutputAssetBlindingFactor, ) + if err != nil { + return false + } - return + proof, err := secp256k1.SurjectionProofParse(ctx, args.Proof) + if err != nil { + return false + } + + return secp256k1.SurjectionProofVerify( + ctx, + proof, + inGenerators, + *outGenerator, + ) } -//ElementsToSatoshiValue method converts Elements value to Satoshi value -func ElementsToSatoshiValue(val [ElementsUnconfidentialValueLength]byte) ( - result uint64, - err error, -) { - if val[0] != byte(1) { - err = errors.New("invalid prefix") - return +func inAssetGenerators(inAssets, inAssetBlinders [][]byte) ([]secp256k1.Generator, error) { + ctx, _ := secp256k1.ContextCreate(secp256k1.ContextBoth) + defer secp256k1.ContextDestroy(ctx) + + inGenerators := make([]secp256k1.Generator, 0, len(inAssets)) + for i, inAsset := range inAssets { + gen, err := secp256k1.GeneratorGenerateBlinded( + ctx, + inAsset, + inAssetBlinders[i], + ) + if err != nil { + return nil, err + } + inGenerators = append(inGenerators, *gen) } - reverseValueBuffer := bufferutil.ReverseBytes(val[1:]) - d := bufferutil.NewDeserializer(bytes.NewBuffer(reverseValueBuffer)) - result, err = d.ReadUint64() - return + return inGenerators, nil +} + +func outAssetGenerator(outAsset, outAssetBlinder []byte) (*secp256k1.Generator, error) { + res, err := inAssetGenerators([][]byte{outAsset}, [][]byte{outAssetBlinder}) + if err != nil { + return nil, err + } + outGenerator := res[0] + return &outGenerator, nil } -// CommitmentFromBytes parses a raw commitment. -// This should be moved into go-secp256k1-zkp library, check out -// https://github.com/vulpemventures/go-elements/pull/79#discussion_r435315406 -func CommitmentFromBytes(commit []byte) (*secp256k1.Commitment, error) { +func inFixedTags(inAssets [][]byte) ([]secp256k1.FixedAssetTag, error) { ctx, _ := secp256k1.ContextCreate(secp256k1.ContextBoth) defer secp256k1.ContextDestroy(ctx) - return secp256k1.CommitmentParse(ctx, commit) + + fixedInputTags := make([]secp256k1.FixedAssetTag, 0, len(inAssets)) + for _, inTag := range inAssets { + fixedAssetTag, err := secp256k1.FixedAssetTagParse(inTag) + if err != nil { + return nil, err + } + fixedInputTags = append(fixedInputTags, *fixedAssetTag) + } + return fixedInputTags, nil +} + +func outFixedTag(outAsset []byte) (*secp256k1.FixedAssetTag, error) { + res, err := inFixedTags([][]byte{outAsset}) + if err != nil { + return nil, err + } + outFixedTag := res[0] + return &outFixedTag, nil +} + +func calcAssetHash(in *transaction.TxInput) ([]byte, error) { + iss, err := transaction.NewTxIssuanceFromInput(in) + if err != nil { + return nil, err + } + return iss.GenerateAsset() +} + +func calcTokenHash(in *transaction.TxInput) ([]byte, error) { + iss, err := transaction.NewTxIssuanceFromInput(in) + if err != nil { + return nil, err + } + return iss.GenerateReissuanceToken(1) +} + +func calcIssuance(in *transaction.TxInput) *transaction.TxIssuanceExtended { + var issuance *transaction.TxIssuanceExtended + if in.Issuance.IsReissuance() { + issuance = transaction.NewTxIssuanceFromEntropy(in.Issuance.AssetEntropy) + } else { + issuance = transaction.NewTxIssuanceFromContractHash(in.Issuance.AssetEntropy) + issuance.GenerateEntropy(in.Hash, in.Index) + } + return issuance } diff --git a/confidential/confidential_test.go b/confidential/confidential_test.go index 298e428..f9d9e28 100644 --- a/confidential/confidential_test.go +++ b/confidential/confidential_test.go @@ -1,15 +1,14 @@ package confidential import ( - "crypto/rand" "encoding/hex" "encoding/json" "io/ioutil" - "math/big" "strconv" "testing" "github.com/stretchr/testify/assert" + "github.com/vulpemventures/go-elements/transaction" ) var tests map[string]interface{} @@ -28,6 +27,11 @@ func setUp() error { return nil } +func h2b(str string) []byte { + buf, _ := hex.DecodeString(str) + return buf +} + func TestUnblindOutput(t *testing.T) { err := setUp() if !assert.NoError(t, err) { @@ -37,80 +41,104 @@ func TestUnblindOutput(t *testing.T) { vectors := tests["unblindOutput"].([]interface{}) for _, testVector := range vectors { v := testVector.(map[string]interface{}) - scriptPubkeyStr := v["scriptPubkey"].(string) - assetGeneratorStr := v["assetGenerator"].(string) - blindingPrivkeyStr := v["blindingPrivkey"].(string) - ephemeralPubkeyStr := v["ephemeralPubkey"].(string) - valueCommitmentStr := v["valueCommitment"].(string) - rangeproofStr := v["rangeproof"].(string) - - ephemeralPubkey, err := hex.DecodeString(ephemeralPubkeyStr) - if !assert.NoError(t, err) { - t.FailNow() - } - - blindingPrivkey, err := hex.DecodeString(blindingPrivkeyStr) - if !assert.NoError(t, err) { - t.FailNow() - } - rangeproof, err := hex.DecodeString(rangeproofStr) - if !assert.NoError(t, err) { - t.FailNow() - } + nonce := h2b(v["ephemeralPubkey"].(string)) + blindingPrivkey := h2b(v["blindingPrivkey"].(string)) + rangeproof := h2b(v["rangeproof"].(string)) + valueCommitment := h2b(v["valueCommitment"].(string)) + assetCommitment := h2b(v["assetGenerator"].(string)) + scriptPubkey := h2b(v["scriptPubkey"].(string)) - commitment, err := hex.DecodeString(valueCommitmentStr) - if !assert.NoError(t, err) { - t.FailNow() + txOut := &transaction.TxOutput{ + Nonce: nonce, + RangeProof: rangeproof, + Value: valueCommitment, + Asset: assetCommitment, + Script: scriptPubkey, + SurjectionProof: make([]byte, 64), // not important, we can zero this } - assetGenerator, err := hex.DecodeString(assetGeneratorStr) + output, err := UnblindOutputWithKey(txOut, blindingPrivkey) if !assert.NoError(t, err) { t.FailNow() } - scriptPubkey, err := hex.DecodeString(scriptPubkeyStr) - if !assert.NoError(t, err) { - t.FailNow() - } + expected := v["expected"].(map[string]interface{}) + value, _ := strconv.Atoi(expected["value"].(string)) + assetStr := expected["asset"].(string) + valueBlindingFactor := expected["valueBlindingFactor"].(string) + assetBlindingFactor := expected["assetBlindingFactor"].(string) - nonce, err := NonceHash(ephemeralPubkey, blindingPrivkey) - if !assert.NoError(t, err) { - t.FailNow() - } + assert.Equal(t, uint64(value), output.Value) + assert.Equal(t, assetBlindingFactor, hex.EncodeToString(output.AssetBlindingFactor)) + assert.Equal(t, assetStr, hex.EncodeToString(output.Asset)) + assert.Equal(t, valueBlindingFactor, hex.EncodeToString(output.ValueBlindingFactor[:])) + } +} - input := UnblindOutputArg{ - Nonce: nonce, - Rangeproof: rangeproof, - ValueCommitment: commitment, - AssetCommitment: assetGenerator, - ScriptPubkey: scriptPubkey, - } +func TestUnblindIssuance(t *testing.T) { + err := setUp() + if !assert.NoError(t, err) { + t.FailNow() + } - output, err := UnblindOutput(input) + vectors := tests["unblindIssuance"].([]interface{}) + for _, testVector := range vectors { + v := testVector.(map[string]interface{}) + inHash := h2b(v["inHash"].(string)) + inIndex := uint32(v["inIndex"].(float64)) + assetBlindingNonce := h2b(v["nonce"].(string)) + assetEntropy := h2b(v["entropy"].(string)) + assetAmountCommitment := h2b(v["assetAmountCommitment"].(string)) + tokenAmountCommitment := h2b(v["tokenAmountCommitment"].(string)) + assetRangeProof := h2b(v["assetRangeProof"].(string)) + tokenRangeProof := h2b(v["tokenRangeProof"].(string)) + + b := v["blindingPrvKeys"].(map[string]interface{}) + blindKeys := [][]byte{h2b(b["asset"].(string)), h2b(b["token"].(string))} + + txIn := &transaction.TxInput{ + Hash: inHash, + Index: inIndex, + Issuance: &transaction.TxIssuance{ + AssetBlindingNonce: assetBlindingNonce, + AssetEntropy: assetEntropy, + AssetAmount: assetAmountCommitment, + TokenAmount: tokenAmountCommitment, + }, + IssuanceRangeProof: assetRangeProof, + InflationRangeProof: tokenRangeProof, + } + + unblinded, err := UnblindIssuance(txIn, blindKeys) if !assert.NoError(t, err) { t.FailNow() } expected := v["expected"].(map[string]interface{}) + for key := range expected { + var want map[string]interface{} + var got *UnblindOutputResult + if key == "asset" { + want = expected["asset"].(map[string]interface{}) + got = unblinded.Asset + } else { + want = expected["token"].(map[string]interface{}) + got = unblinded.Token + } - valueStr := expected["value"].(string) - value, err := strconv.Atoi(valueStr) - if !assert.NoError(t, err) { - t.FailNow() - } + value, _ := strconv.Atoi(want["value"].(string)) + asset := want["asset"].(string) + valueBlindingFactor := want["valueBlindingFactor"].(string) + assetBlindingFactor := want["assetBlindingFactor"].(string) - assetStr := expected["asset"].(string) - valueBlindingFactor := expected["valueBlindingFactor"].(string) - assetBlindingFactor := expected["assetBlindingFactor"].(string) - - assert.Equal(t, output.Value, uint64(value)) - assert.Equal(t, hex.EncodeToString(output.AssetBlindingFactor), assetBlindingFactor) - assert.Equal(t, hex.EncodeToString(output.Asset), assetStr) - assert.Equal(t, hex.EncodeToString(output.ValueBlindingFactor[:]), valueBlindingFactor) + assert.Equal(t, uint64(value), got.Value) + assert.Equal(t, valueBlindingFactor, hex.EncodeToString(got.ValueBlindingFactor)) + assert.Equal(t, asset, hex.EncodeToString(got.Asset)) + assert.Equal(t, assetBlindingFactor, hex.EncodeToString(got.AssetBlindingFactor)) + } } } - func TestFinalValueBlindingFactor(t *testing.T) { err := setUp() if !assert.NoError(t, err) { @@ -122,66 +150,44 @@ func TestFinalValueBlindingFactor(t *testing.T) { v := testVector.(map[string]interface{}) inValuesSlice := v["inValues"].([]interface{}) - inValues := make([]uint64, 0) + inValues := make([]uint64, 0, len(inValuesSlice)) for _, val := range inValuesSlice { - n, err := strconv.ParseUint(val.(string), 10, 64) - if !assert.NoError(t, err) { - t.FailNow() - } + n, _ := strconv.ParseUint(val.(string), 10, 64) inValues = append(inValues, n) } outValuesSlice := v["outValues"].([]interface{}) - outValues := make([]uint64, 0) + outValues := make([]uint64, 0, len(outValuesSlice)) for _, val := range outValuesSlice { - n, err := strconv.ParseUint(val.(string), 10, 64) - if !assert.NoError(t, err) { - t.FailNow() - } + n, _ := strconv.ParseUint(val.(string), 10, 64) outValues = append(outValues, n) } inGeneratorsSlice := v["inGenerators"].([]interface{}) - inGenerators := make([][]byte, 0) + inGenerators := make([][]byte, 0, len(inGeneratorsSlice)) for _, val := range inGeneratorsSlice { - gen, err := hex.DecodeString(val.(string)) - if !assert.NoError(t, err) { - t.FailNow() - } - inGenerators = append(inGenerators, gen) + inGenerators = append(inGenerators, h2b(val.(string))) } outGeneratorsSlice := v["outGenerators"].([]interface{}) - outGenerators := make([][]byte, 0) + outGenerators := make([][]byte, 0, len(outGeneratorsSlice)) for _, val := range outGeneratorsSlice { - gen, err := hex.DecodeString(val.(string)) - if !assert.NoError(t, err) { - t.FailNow() - } - outGenerators = append(outGenerators, gen) + outGenerators = append(outGenerators, h2b(val.(string))) } inFactorsSlice := v["inFactors"].([]interface{}) - inFactors := make([][]byte, 0) + inFactors := make([][]byte, 0, len(inFactorsSlice)) for _, val := range inFactorsSlice { - gen, err := hex.DecodeString(val.(string)) - if !assert.NoError(t, err) { - t.FailNow() - } - inFactors = append(inFactors, gen) + inFactors = append(inFactors, h2b(val.(string))) } outFactorsSlice := v["outFactors"].([]interface{}) - outFactors := make([][]byte, 0) + outFactors := make([][]byte, 0, len(outFactorsSlice)) for _, val := range outFactorsSlice { - gen, err := hex.DecodeString(val.(string)) - if !assert.NoError(t, err) { - t.FailNow() - } - outFactors = append(outFactors, gen) + outFactors = append(outFactors, h2b(val.(string))) } - input := FinalValueBlindingFactorArg{ + args := FinalValueBlindingFactorArgs{ InValues: inValues, OutValues: outValues, InGenerators: inGenerators, @@ -190,7 +196,7 @@ func TestFinalValueBlindingFactor(t *testing.T) { OutFactors: outFactors, } - factor, err := FinalValueBlindingFactor(input) + factor, err := FinalValueBlindingFactor(args) if !assert.NoError(t, err) { t.FailNow() } @@ -209,17 +215,8 @@ func TestAssetCommitment(t *testing.T) { vectors := tests["assetCommitment"].([]interface{}) for _, testVector := range vectors { v := testVector.(map[string]interface{}) - assetStr := v["asset"].(string) - factorStr := v["factor"].(string) - asset, err := hex.DecodeString(assetStr) - if !assert.NoError(t, err) { - t.FailNow() - } - - factor, err := hex.DecodeString(factorStr) - if !assert.NoError(t, err) { - t.FailNow() - } + asset := h2b(v["asset"].(string)) + factor := h2b(v["factor"].(string)) commitment, err := AssetCommitment(asset, factor) if !assert.NoError(t, err) { @@ -227,7 +224,7 @@ func TestAssetCommitment(t *testing.T) { } expected := v["expected"].(string) - assert.Equal(t, hex.EncodeToString(commitment[:]), expected) + assert.Equal(t, expected, hex.EncodeToString(commitment[:])) } } @@ -240,24 +237,9 @@ func TestValueCommitment(t *testing.T) { vectors := tests["valueCommitment"].([]interface{}) for _, testVector := range vectors { v := testVector.(map[string]interface{}) - valueStr := v["value"].(string) - generatorStr := v["generator"].(string) - factorStr := v["factor"].(string) - - value, err := strconv.ParseUint(valueStr, 10, 64) - if !assert.NoError(t, err) { - t.FailNow() - } - - factor, err := hex.DecodeString(factorStr) - if !assert.NoError(t, err) { - t.FailNow() - } - - generator, err := hex.DecodeString(generatorStr) - if !assert.NoError(t, err) { - t.FailNow() - } + value, _ := strconv.ParseUint(v["value"].(string), 10, 64) + factor := h2b(v["factor"].(string)) + generator := h2b(v["generator"].(string)) valueCommitment, err := ValueCommitment(value, generator, factor) if !assert.NoError(t, err) { @@ -265,7 +247,7 @@ func TestValueCommitment(t *testing.T) { } expected := v["expected"].(string) - assert.Equal(t, hex.EncodeToString(valueCommitment[:]), expected) + assert.Equal(t, expected, hex.EncodeToString(valueCommitment[:])) } } @@ -278,67 +260,29 @@ func TestRangeProof(t *testing.T) { vectors := tests["rangeProof"].([]interface{}) for _, testVector := range vectors { v := testVector.(map[string]interface{}) - valueStr := v["value"].(string) - value, err := strconv.ParseUint(valueStr, 10, 64) - if !assert.NoError(t, err) { - t.FailNow() - } - - blindingPubkeyStr := v["blindingPubkey"].(string) - blindingPubkey, err := hex.DecodeString(blindingPubkeyStr) - if !assert.NoError(t, err) { - t.FailNow() - } - - scriptPubkeyStr := v["scriptPubkey"].(string) - scriptPubkey, err := hex.DecodeString(scriptPubkeyStr) - if !assert.NoError(t, err) { - t.FailNow() - } - assetStr := v["asset"].(string) - asset, err := hex.DecodeString(assetStr) - if !assert.NoError(t, err) { - t.FailNow() - } + value, _ := strconv.ParseUint(v["value"].(string), 10, 64) + blindingPubkey := h2b(v["blindingPubkey"].(string)) + scriptPubkey := h2b(v["scriptPubkey"].(string)) + asset := h2b(v["asset"].(string)) + assetBlindingFactor := h2b(v["assetBlindingFactor"].(string)) + ephemeralPrivkey := h2b(v["ephemeralPrivkey"].(string)) + valueCommitment := h2b(v["valueCommitment"].(string)) - assetBlindingFactorStr := v["assetBlindingFactor"].(string) - assetBlindingFactor, err := hex.DecodeString(assetBlindingFactorStr) - if !assert.NoError(t, err) { - t.FailNow() - } - - ephemeralPrivkeyStr := v["ephemeralPrivkey"].(string) - ephemeralPrivkey, err := hex.DecodeString(ephemeralPrivkeyStr) - if !assert.NoError(t, err) { - t.FailNow() - } - - valueCommitmentStr := v["valueCommitment"].(string) - valueCommitment, err := hex.DecodeString(valueCommitmentStr) - if !assert.NoError(t, err) { - t.FailNow() - } - - valueBlindingFactorStr := v["valueBlindingFactor"].(string) - valueBlindingFactor, err := hex.DecodeString(valueBlindingFactorStr) - if !assert.NoError(t, err) { - t.FailNow() - } - var valueBlindingFactorArray [32]byte - copy(valueBlindingFactorArray[:], valueBlindingFactor[:]) + var valueBlindingFactor32 [32]byte + copy(valueBlindingFactor32[:], h2b(v["valueBlindingFactor"].(string))) nonce, err := NonceHash(blindingPubkey, ephemeralPrivkey) if !assert.NoError(t, err) { t.FailNow() } - input := RangeProofArg{ + args := RangeProofArgs{ Value: value, Nonce: nonce, Asset: asset, AssetBlindingFactor: assetBlindingFactor, - ValueBlindFactor: valueBlindingFactorArray, + ValueBlindFactor: valueBlindingFactor32, ValueCommit: valueCommitment, ScriptPubkey: scriptPubkey, MinValue: 1, @@ -346,13 +290,13 @@ func TestRangeProof(t *testing.T) { MinBits: 36, } - proof, err := RangeProof(input) + proof, err := RangeProof(args) if !assert.NoError(t, err) { t.FailNow() } expectedStr := v["expected"].(string) - assert.Equal(t, hex.EncodeToString(proof[:]), expectedStr) + assert.Equal(t, expectedStr, hex.EncodeToString(proof[:])) } } @@ -366,45 +310,21 @@ func TestSurjectionProof(t *testing.T) { for _, testVector := range vectors { v := testVector.(map[string]interface{}) - seedStr := v["seed"].(string) - seed, err := hex.DecodeString(seedStr) - if !assert.NoError(t, err) { - t.FailNow() - } - - outputAssetStr := v["outputAsset"].(string) - outputAsset, err := hex.DecodeString(outputAssetStr) - if !assert.NoError(t, err) { - t.FailNow() - } - - outputAssetBlindingFactorStr := v["outputAssetBlindingFactor"].(string) - outputAssetBlindingFactor, err := hex.DecodeString(outputAssetBlindingFactorStr) - if !assert.NoError(t, err) { - t.FailNow() - } - + seed := h2b(v["seed"].(string)) + outputAsset := h2b(v["outputAsset"].(string)) + outputAssetBlindingFactor := h2b(v["outputAssetBlindingFactor"].(string)) inputAssetsSlice := v["inputAssets"].([]interface{}) - inputAssets := make([][]byte, 0) + inputAssets := make([][]byte, 0, len(inputAssetsSlice)) for _, val := range inputAssetsSlice { - a, err := hex.DecodeString(val.(string)) - if !assert.NoError(t, err) { - t.FailNow() - } - inputAssets = append(inputAssets, a) + inputAssets = append(inputAssets, h2b(val.(string))) } - inputAssetBlindingFactorsSlice := v["inputAssetBlindingFactors"].([]interface{}) - inputAssetBlindingFactors := make([][]byte, 0) + inputAssetBlindingFactors := make([][]byte, 0, len(inputAssetBlindingFactorsSlice)) for _, val := range inputAssetBlindingFactorsSlice { - a, err := hex.DecodeString(val.(string)) - if !assert.NoError(t, err) { - t.FailNow() - } - inputAssetBlindingFactors = append(inputAssetBlindingFactors, a) + inputAssetBlindingFactors = append(inputAssetBlindingFactors, h2b(val.(string))) } - input := SurjectionProofArg{ + args := SurjectionProofArgs{ OutputAsset: outputAsset, OutputAssetBlindingFactor: outputAssetBlindingFactor, InputAssets: inputAssets, @@ -412,31 +332,48 @@ func TestSurjectionProof(t *testing.T) { Seed: seed, } - factor, ok := SurjectionProof(input) + factor, ok := SurjectionProof(args) assert.Equal(t, true, ok) expectedFactor := v["expected"].(string) assert.Equal(t, expectedFactor, hex.EncodeToString(factor[:])) - } - } -func TestSatoshiToElementsValueAndBack(t *testing.T) { - bigInt, err := rand.Int(rand.Reader, big.NewInt(1000000000)) - if err != nil { - panic(err) - } - satoshi := bigInt.Uint64() - elementsValue, err := SatoshiToElementsValue(satoshi) +func TestVerifySurjectionProof(t *testing.T) { + err := setUp() if !assert.NoError(t, err) { t.FailNow() } - satoshiValue, err := ElementsToSatoshiValue(elementsValue) - if !assert.NoError(t, err) { - t.FailNow() - } + vectors := tests["verifySurjectionProof"].([]interface{}) + for _, testVector := range vectors { + v := testVector.(map[string]interface{}) - assert.Equal(t, satoshi, satoshiValue) + proof := h2b(v["proof"].(string)) + outputAsset := h2b(v["outputAsset"].(string)) + outputAssetBlindingFactor := h2b(v["outputAssetBlindingFactor"].(string)) + inputAssetsSlice := v["inputAssets"].([]interface{}) + inputAssets := make([][]byte, 0, len(inputAssetsSlice)) + for _, val := range inputAssetsSlice { + inputAssets = append(inputAssets, h2b(val.(string))) + } + inputAssetBlindingFactorsSlice := v["inputAssetBlindingFactors"].([]interface{}) + inputAssetBlindingFactors := make([][]byte, 0, len(inputAssetBlindingFactorsSlice)) + for _, val := range inputAssetBlindingFactorsSlice { + inputAssetBlindingFactors = append(inputAssetBlindingFactors, h2b(val.(string))) + } + + args := VerifySurjectionProofArgs{ + OutputAsset: outputAsset, + OutputAssetBlindingFactor: outputAssetBlindingFactor, + InputAssets: inputAssets, + InputAssetBlindingFactors: inputAssetBlindingFactors, + Proof: proof, + } + isValid := VerifySurjectionProof(args) + + expectedValid := v["expected"].(bool) + assert.Equal(t, expectedValid, isValid) + } } diff --git a/confidential/data/confidential.json b/confidential/data/confidential.json index d719368..f70f2dc 100644 --- a/confidential/data/confidential.json +++ b/confidential/data/confidential.json @@ -15,6 +15,36 @@ } } ], + "unblindIssuance": [ + { + "inHash": "d45bad6e96dea76fc557489498d91cab564602062353fab12e1f882b707d0183", + "inIndex": 0, + "nonce": "0000000000000000000000000000000000000000000000000000000000000000", + "entropy": "0000000000000000000000000000000000000000000000000000000000000000", + "assetAmountCommitment": "093a2de000712b51310307db6fcf17633cf17aee7bcdbaf735f12a606c8bce4f0f", + "tokenAmountCommitment": "085890d0f522870e6709754786890b7efda5212c886be73f2e2b505b1e00425c6a", + "assetRangeProof": "60330000000000000001dd219b015262d0d63b9d9eb6459a1ba990488cccbc1b08c8d28b3749a5993e2596194f4d0cd183b67ef56d64640f3de75d93428b34c22f5dddc17338c017b2daa60e5d61549a26256182f512c49ef91b97bd4cd20f2c8f98a5355c419915bdc51fe9fdca2d626db85212075a0bbd221aa31d9800523f20ef4a22762ebda7ad2bd13c0deb995b96d2d44d5dc97e18a028d543299bd891a605b41d64910a73531a236b19e722d55019dc07fffb94324d8343c9ba3b4e231c956ec49545f1dcfde57b32f9ff04e4594c994055ed3f43544e2b90795182d37735f796f8310ea8ef85466e954ee61b1ac6cbdf0b3367414aa33b1a6ecd445009b53331632ea6a2e01c71579f90eb51dd07efdd0d6c8c21d3ea8b58bcacd4b5b9682ec6e2472846525dc6a8c99acd4b4fa73e5f5f80f811496153ec2944b8a36f4304cb287df7a0ef2dc05c37da91afa56f5230d21cff4dbf32fb3f2354ea1d4c7d54bd3545fde1d9034dbc7702ae9a89468607feb4b35adfdae07a3c44a8619567b494c3619ec8a4660042c60057e990969af5a9f036c7adfc89e6e8718d3432e954cf5859bd31eb12c0037ea0c9b2345d4c37fa2c62a5125b2e5c1e5d243da81e0190e32969b4837672aa3362e127f1654468a2b8e08ec5f6248de46f43d432b4742a313d76addc4113c192f06651932a538ba7b48431d73fa075809c6db2406b035949bdd1aef9dff80b46bb01d61c21b007cef33dee96d39476078f634ea6c3082f0b0b1766860eeb9c304c352a12d8e3e412a1fa7041f8c8c3452780315dd2cfd423930ea34025ab327ea6cfe4e74cdbf5f7b443f356342bfdcd28b329364a307e55a1750ec3852f3157e3187e49d0de5c58853318354e66fc9339b0e8b24d68b7118f3e3371a7646bb5c9ce77fedf19494d7bd8ae0df878bee1c3aff262eda2dc541dcbabd662e409341d348421f28896371d1f506aacd9c1b0fc4e0a40151222f551f4f07ceb492269c025612179b07786eedcf439f88441d035e359aa246306bfb31eed4769b921ed25264473fa7387e197da75e28e294418bccaa769c16599f4418e2c99456bfe52a92218669d76d553482ceb7d9ad52ca8d2c5c126a8eafe5df5fc0efd63762679bda13491628b306950e106bb6e543748a1ad6a444874095c4344f8c3f2ad5d34e867c694b643dabaa554f8a2db7f24f0a6fd2046cd08284859655e00112226c49b98519f789fdf2abd99fbe8d14376dc1c091a5c6555ca58400d08f565c0cfc80481bce87bcfb4ae715c273f2f3eb9a1215eec127c383c34e556c04c0c83e5a5c9d8f61338c2670d8fa33e3a91100ba98f6ba6c1936d26d352f656ac230f729bb086111fc4b90e73d49558cae1d5d88c42823b7e83d2dc0d44cccc825fbf8e6039d874d0aa9870b8af623f319de6257fb7e7910278801a77aac8922593b87eae2294e865f99f512857eb575075ee2639538fb813decc370003b88563f58cea1e53e5feef165c5d45a36d62b01463c37424411c475c92389b05a3b7610120dab3e6d3219c3415da89487639e1a028793e0c7d6886bb6efad00c97d3ab4e128f61252cc26b6b10a0faf1b7ad93a69eb803fad43698dc1a68a4f547a07056aea8f3cba5a25f8f4567453039c4472d8ce009db01b6ca1bde97dfd522adea64b7c9e5c11f21ffbac4c268983d0b17f94d6bc74c0a69dcce7c098e32f6f4c0d7f2e20344c8d825f25e8466613ad35df70ee21599b671f74bc9c591525fb0a7812b2582f1f27d4986882d9ffdd9e74efdddbb852b1b1b742457d492365fb0d24f3bbb6e0aeae39a44e2a5ec82ceb97aaa583dd54f448260792345d601f1f3eb474be5a4e04b8ca41801832244bfe00e24aca0eb070d805b8655c3108f88842155dc0c138e0fb14e5066a67be9e8f726337c4cfaa6d5df38e33d4461137501f783168eabf546b828b25b5dee52a8953ada300e37c9af979fcdb73f384d964318d9003064a707f8c0667c10e6c81a1329c82f229adae37f046335461765358dcbebd761924ecf0360a71769a2d018267f9140a535f5ee072886a610df8c8d5a920a25b4023bca83872faee636a0cc84d5d59ef262ea835082309b77d33fe1d8b6fe9cd1e6f54189e62c4b85a639075a4e382225e761d9b0defe0622ea89b35b503634bba853d8050ab04b86689a0daa8857825fb994db5a4e66af6ae91cf39c15a5fb29692c3801c903153c26110f9b8ac463e5f1eda15ed88ffee5847f2443fc1a6adfb4704f52985c19cbf3081ddfe839e74a1f3cdcc54e022996addfb0cd0d439b53affc915b9f33e83091791e0e033587e5fbf9220b8ce86b9b74e9513f967571bd0b7ce61d116511aac4cb3e0b3f81211d3db672d3efc385652f166b6a3a5a56b529847bb328e27ebed4c01bc87227ca77b3903d2599fdf7b059e56dcc27d01b667f596ec1e2219dcb2720f99b2ca39589ebcb5362fca14926950fab04eb848bdec4d1c079e44ad30752cbc7c4b611516a6cde70de4dd7701847e99a24ba6f37635d284c2567a6407844de16185380f4e1b972e0f557926f77a307e2b39e1e59568983dd2cc56998d06f4c7f1ebc46fd12111ca65882eb6b8e891d7b22b5576394799858957c26252c9b0197e4758c85cb35b2c6dd0c4aff79810462a265bfe3d62c72ab4fdf00c4042b91ff7343e167d0f5a8df0b95abca1c08fc7187d05393d25a2735eaeb746e64cacf0aefbda5b4d826a2f394164aba87dae2e8bfc98b5290192e9c59917d345e127dd13d6cb4ca9274ffa620c270d9048e5217c377275a26810828a9140db00c6565029302cc2ee955cf951d6c8889ae99ac022d50f746f3b0e14170f77e57fc6bd0a213462057d7bc048f9c3f73c7780cf5db0fd48577544b4c027bddec6e8b33c0702f00fdf894bf4055e4e6679feeda33e89e692720d85f22ee4e6ddd24e092e9d0ec3bf9691d4086a14c6b9cf840561dbc487cf9df926c71544d544ed6729b9a6611036fbd84f6f4f9224f9285c7812cd1723706904b263b511e0bbee71451c949a71e86331842bc814f8fc14af0baedb117e31cc7220b2f20b379b184f5954318f835a2989556d56eadf90fdc6f1e86c5a4d04793147f027c9c36844b6da2aecd34914b0005cb68c0455b2ae252a7f2a1c5c03dfbc38a3fb75858782bb8b15e2af9687461c7c6a23787d91b170f258c76e8238dc324faaaf448a1542f3e901421faa9964da73f33efb2466526286eaf5a6232e3862a0f088d7ba6034ca647fd498b817df556b243e6aad88e022ff6bd53a983d6642d20d09a57302dc1fb6d9cd8be099f3c12a53a97ec2c7954f8fb8df4b2cffffe1d9e135d934eefcf8845c63c3fc5cf358a2336221d834d80c8a9fea6beaca46075aec28124627174d2552f96b4e66c1876676610730823b4cfc10af2a073fe1b88726439cbdb6834d2ab73ba04ae65a6f08da16c1af0ca1b55a917623cd3173a2dde32c87791014160bfc942f1c02a1914deb1cbdc3e2a182948359c61b2d65985495594aef91bf0961eb1fabceaa4a49059bb4ad3d4137974601d9da8b8c3d0ae586b03304c9512508e9b81e2c31f1bdf15b2f8be423d572e3723b2ec237ce5dfbf0a3de9b833057e115471a5021f941ba950178545b024f52bb749657716a472fdf1776d85c97a4856a683b2b44a0d15b028713500eaeca9c06a082c54b6d7208f104a3f6ba491086a918601daf22d38afb56be67a96108fc7bd8e7eb3eb86421004e183c79fba99e3a75139c27e1c29bd9023fffdff1e563f3b3b82424da8d33f137c48760e3ef4bb452fb0cbe64ba680c75714239aef2a67cdaca18abbfdf372bf9c1b1bb06d174ac186a5c8dd5732b17923bc812be3ff2736d420a70f1c69b53ece49c641582cbcbe3c3d8dd4937d08802bf181b26f7c45042178bacb74565b892e08d27178177418c22932f8661766aac0c284db0cc73009e216a51360d8f37d59e72af05223cf98c12e553d92efce1761c1e921de198c79a08161f37764050438efcdf08c7f5bf2caba71cb152656eb5d71b49128c4a78efbef18f5c59078ac7ac79c8b5613f8c574e7a473c68b8f6e17d7f46d27838bbfd324dbe8aacea9f13173ea15b6467761bbbb7dffcd1ee652d1d827e0493e1a7a4ac2c4a9731b3ec7da6714f713b250cf2756e34769469f09d86fd53489ee15ea322bdc9e4951ed9f9091b7deb1c8c2779788faba8ace17f40435290570e6c0fe9fa9885b3e27278ef8418eea4309d2a3bc4a5eb24690a0827ac78fab22d5fd8641f876ead4bbfc487a47e54d07a557f0e852588e4e1f7517e94e606bf448f44ee87d894d7012fea5cffed89c8a25c308e7b3b8454107dc8ae4ab9e55bcb614190550c055d11b8557221789eebb85b233b83ec9c4a306eac447b59dec6c248e78c4e28cde8f4feebf03a3af369fd4960c6175cebae1e3b56bc00f385bc5ab832c5c58dad508260cc8791373392518e76b8acb615e483ec1710136df20568e7e5e05f3a5aeb7b9e290e1875125aca539622959373c150fc3d1b7f3b3c6aacc5c16f3b6e364ed887d432139370cfb82d761cc16c0913b137b51741489bebd1ab3aea03db53434b9d5362a00e72e63d66360ab0eec344798288136723772f20be33bd55c85a1ed45fc19cf5df626d0d015c9839fdca0dc3aa4dff3a0b632e87fed9ee542e2f54efa1226f6124f074a00bcb8061503db610ee0a25190804174591b54c254bf0780001ad370ed7a3783463a89babe54973af2b7b57cc9ad6fc3b4e81b987ae012412e6f43ce91cdd30e253c7c7054aae7608d04e44efe72588719c1bbe160a0d0a84f7401d781297e429a921af31c1716590f9b9513aec4e164fe98b9c5b41a4af0a998c57cdeb97348ba57e438e1f4b2698488c27a4eb77dbeebf71825c603883bf758047927b9cb845bb7db23592b690c4d36ebdcd6f72a8a9cab67cf90c0e28b6ae63fd51a3f720bf87148b40522bb0cc8c3193894f96ec3118dd6bcda30a6d3a9d267f478c9d76d365e7a07e0c9e1c4cc7399f8975de9c7cbc80e93b766e4111d84369a142d3ec2699626127e29028b532e599089eb186c55f061fc8d54813d06792a640f8839fc74dfd9c79f804ad1dd8cf6efc123095407628f4e67c5353ca59eab273a6c0b7ae744c6b2b5ee22d4a9cf20bcad0799fdb4b953a0642f67406f534dd8c9c08b34462e97b5eefbbd61c2463ab3647a4eaf738b941c160ca9041ae0999e1a70240fce8686e9cf9747984a03f3e03057ceafe578d0c5b54fad35c892b41d635f5ba2d472c55033e03a6b48d20f830e3b03b4137f58d170223219c8a41b96149f73e4627591435e6739332542f3fa0b20eb9eb93dfd11cd6dd1776f70e96a655b40e8acc9e6fd10da06caaf66264062d7a288e6142121068ea7b0c9938967fc98313dc27568caea25918ccb88a4a009b68c091442c8fbb113119005f7fe0d3be68813c4fd87e5c997157f1921792d50719a80e78d72345b86774647f879dd9bce5cee16304987d96124087785494f69ca03bd2d4c68d908daa81682b4f423358ac7495836a52015d6e8b9bcee2887af15861ae9022971ee0db5912cb9f57d9494374eefd4360e4d6b8be06a46b975afcbee3404ffdca5e2600148b51e2f47040b4323eb892ff6e3bc42c2c92d55425c3cb86d12799ef781d121bb9b805bdd5572bc18f7389529a377eb294424a542dac8d6800d746f2b0a1d004c22db5937f996225dfad85814aafd6967da756b5cf3a396f29ddf12a4d409811410c7bf5a22a2c5a5db7f40654c9c7129e2c5eb937e2da65fd740fefe132a5531498fab72b07dbf963ef2e422fd1c4cbc88ac4c0046ef17cb51757a9abfbdcd2661f7a1", + "tokenRangeProof": "60330000000000000001b8315000d5940edb5ec16230690ca9dea206c8c9efd2d340cc9ae9d9a1f646ca2292d06602df615b8ff5343ad1626c0584cbf36e90c67dbb8eaad59ad400f287b7926cd1e1fcd408a1c73e9c84121bc7074c5d144982db702654ddcbf0659b2bf2211afb1857b1fd3db674fbe2d70e88c8f4fec9e3e0bfe6fdeb5209efc0c78537f9fa2fca94b24a00b0957f8536b69a1be7c9aff9772de4376e2919aac4bb0b3e789e756a67796175ba7045d46d815a177433a6eb7b616366f88ef104233fd6d6bfaf7d167fe5d44e26526c112aa0019ce44424b693cf801e0b1dc2ff1d1c03c729c29aac3ee8f224108d185468fdd6159330c6d55f84c0c6a45b40bc58758c4b65d17a19c04d20f930efc848282a505ae5d1cf626423149a4f394df218be077f21341e486fe1589f6593e56c45142d5461e261730c124fef9b80d09f75e3c76208643096355299566490f3e979bf327b8bd3058f5376eae982d81ea59614d7498fc78b6596c3824c43cc37b3c1a0d4a475d89b23a69a24d4d97336614b27d57aa4192076d7d9baf4c127b292f1c5374a6d24904ac9d2ac41c14224f3de932416daf1f3266bbe794aa986fe0d8640091622cd6af233084c41d5c4aba605a450d835314ca68c16280624142f05cef47ae3ccc39198cea4e34277ddf46f4c60a6af9c12b557d7873ebee22bf923b6c372fdb50c4f4965a6eb9c0da014d3b704de2b444cc334900f5da7aa203ed8d9cc2d30ca4a2fd388c83d75682a9e7fc17597a3f81eb26deed4adc0d126cf816e24ff6852d039a3107548b42775bb1068398e12ecf903dee0fc40f785090ed1072f2b059ba869475816e1a26c3b67327bd280bfa132075fcd1af31407bb17b881cfd0fa9773f4c9c24822133d705ac482738ed2275cbb9cebe041331a0adb7c3f3764770faef82d3d0e62fb86821405aa13b8c144ad9625d992f3ce3933c60f0b1258ffc2ef81406025725b3c7ff5aa2fd985a3df73c6a15d1bd0c6bdf22b396cad21ed36bd73f69766f7108ff2f87c4a86338c0ade56e2b52e3741b4e67e21c9fdeaef40b76cda5083747d34d1381c0c705c6dacb6aee172ded9d0a9fc627c65738f2bf7bcdf6a5c160f17119ad99de9b6cfb737994d4891b0eb2f600e82ee05c5a08bd95722e964f81ff5a00268293261c28672e480c4ca224868093d0a25c46f4677177b3644944c6fb9b1248b132047a49240e15335f9bf931d14496f09e19d4a71ba2d82bb06570d193e0080ad33e002ae9cd53cf9b08f7b8cc0b2ffd1fcb85826995bc380bed3294d5d58964c47e10aabd73577391fd379ac3ba493d731b6be38b02991f0d35b90d2cc4efbc29ade4b7e0b383ac3ca62ed69cababf0f2d53a14551443eba16de884382728d0f964746ec6ad8abf736068a1c1eeec411a15e34ad17ec0d6eab1b68cf694f337c692bc355cc0472045b473b39cd5a00a994032effbff11e3373f8687e629c602ae754769c40d03c2d0c2be266d23625364fb37daef452de0d1da1854045c19e66f8d9ad4e16a0e7012f6806133b9980752c1a821d6ad8ebfc05a576a1a902ec10806615ad4cd4f1c3f59f9d3a7b1a47d349ecef27cd22efac553977a70d20404652f03dc07d1842a0bf7136501d0c172997279c0753ffd6eaf867078a0fd71e575b46261e33e35c80b26bea71ef74f8e239aaa2859577fd36440cced28d0ff08f346857bc7b82dfb18453bb6ef63c38aa82bcd3676bfae67c0f6980288f918ceead9d52efb50b3d322dd23f5ed7fa96a5d65dd1777d18ae8c030a45bd46c221d74d46013c9b841282c83ef6c30ddd199c2995027a9a7d8a477b31f854bcb244c5c56a893685f5da453a81a3d168ffb1fb6ddde0a6e98b0c05f1a1d7515c72ebaf2dcc3db565cde79e36ecf28a1b1454a93fde7a2c2e2a360b20a0b3cf5da66c86d7d3a8480c2259ef5039ef5fe0667f94af846147ad14b817643fa93fc878797075386952f98c2e26fe04c4587565ed40ddb15d299c10d829c8be7f9f11fc66d8ff770f7c93f2c83f8ca55aa6ac7345d985f0420a68d3d6276c36ff9de0cb360bf956dfd59fc9188c4a75b385fa1d7f396b4d2fe372a27e8656f891719f819e2ea7029786fe1ba93886f4a9434dd0e14def64c94c01b1578e95813af6f4b7f25d76304c643b9bbcacbf413cfe1753c269155b26add1e7778e52bde01e43e719d004b7d5e287a3b2b63bb6843cf3577ead5bc320aa948dfa91a9206d0499818d261d9fdd053a5869d5dc210df79fd1e5b82379fe118695ee2332d2a193cd9b6e9b59fb71d633d31ad2a6a042a07a7855796cf3022cb706bce1bd04cbd5be9447090ddcbc076732224a8690a7481e4e8d9f1977812a84e101924670d9f3df1b6f7461f7851451fe739cc35fc1c52ac0f824fd1ea0285d21cbb5356fa2e393609e42f8e6afc79b74b5584fe65ce7ddea51188ddf53d7a4d0005c36449e873dd8101bac779809af8c27b8446088d747032be0b71d2167eda2ecc500498ded70487644cfb3e0e8c2d9d7a5b605f746d9e60b444cafc1961a59239e3e3922096a5eb2140162dd4f0fa3755c9858524cd2980b45756140690d19de298c4240a9d57669e42531182f9e9ab7a9f05a3c0bc1b6df76d418004b5ef4be2cfed2ff5b614720cf25492a8523c7e8a747253081d3a2ae26ce567df69a87446c076832d88fa5968cd766c21ef536110a29e0709e3ee2e2e71d15a71892afb1ec089f3544b95d5b09738286735385bf48c1f8aa6ccfdbbe29f9d009d8cfccacb7ac3db799f38e5d32e6ff340e0bbc9d9606ff5c67d631672f9798b6828120240b4a35c99b133ecd6ac64319b721cad04fa296b6cbebda843fcfa0fc8e415b321ef60099d0e54e9f1a90576bd718265e3b3711b56f629f15cf2e8ed584dbfca37b38247823c0c61c864468f7fc25c69f53cd05b0ade7c6bdaa13608090e59b58ec835658a37f06f237f37dbd8510cc6fe8194b2fad6ccaea42e58ade6a237bed7a91a9a1b8b4bc20a2ca3009b06bb6c5bf507ba52c1b04d1a175846333c0ff636d928189aaf4880cbbf99db477acf8bf980f9c2da7ae832fe62c5c0e0f168c69004d0c1926175925a22e84d831dbc32233b020b14796e44eb73fb62826789c63d4fa6dd525136b11ce1fdaa3e9d7c3518d6ac91285c87ee0943f53a51f383807a07569c84b266921b9502431e701e95b53d2ac4f0bd34cd1c49bf8068a0bf4f53ad234523097723df128ae6a8af7132771fe9fe1ba8198abf83cbd5fcbaf656cf2d29213183d002a67e0818489b78e82c0332733abc8861a9e8684df03c36601b1ec945e6d8cfffffb7d5c5fc1f6cc482634185b4aa561d329a8c78f462475decfc6d7168cb7f0abad63c9a5009904d67022e3cb1befa1269a9c30c3d1412e3bcb998a291f79b169764d34c8adaa176498db83e71c6d193854abbe862442ea71e98f97f18c539248c177d2f4f6aec00319837699585224eddd64550b7186cc4b617158b4efa1551466c90f126602e6337c5d546513940f9a45c492c9f0966d684e6f720c4a94699d8b5d5f6818be0588d36442c5250ece7503834f4e8c0e527340baca641659c0ac26849362fba45ecee78791402685254b443f2504dc33983da0e446b05abb66bfdcc000229ed8572337fff151903bd676fff48f6232f33c291ea7b976c7a35e068abb40ba408e3bfbee1fcba9db6e129c2707ecc3f464dbd2048c30ff76a8c898c6adb793ca934299d0da42e69c14589475cb09cea300cbf1680867d33f59a854530eb0ce06821305e845a36e783f81c9ad602a93f3048070e2be3428167c2e421819c1d60a673967e826445ae3413f21296c3954941b2c6d6d6eb34d866b000a35e6dcbc3d7e5040a5f8c68a3a17abd2d1fe1361b1a532e61cae9e28d0a85a62d37b3a5971deccd1b5fc42a58d3bcf317f9dae78c1a95536e418f81a0bfc2d70baf07422ae14069ee52e38df68adc41c5eac14d3445be5458af418013cc81134bf4d507538dc079430355b08fe9b8e7e4aba6f48e27c82615aa1a8daf89185e6c5fb71d2a5d35295ad22787e3f09522b033153cf6d29539cb53a0d7d39cfe1b1c14042df4d10d5aafded9f024b59b2e2e97cadc0883f8d2694444e360d778841b87bb0601ee5fa9e6e4b13bd8ca312871d0e5d2b2b2ba42d53eda520ddb894b37d520397700f46e6ba5bc3078cb7e08c100397f366ec26e4dfee408c510180b4e7a9639e5e9508980570c8a11bf64a9e63af5c2b293eea9e83796441163c32ad2f24872238028cf510caf0e2b20ff7b08e81067dbcf6af5bf346d87d27b8d7c3674bf6dc5ad40508d0d10782512b4fd3febb6762803842cd2b0c9f634293f40d9e9de986ec70700c06d9f65fbb91cf3f9d33f9bbc57cdf4f8308c1b007523c7de14d7c6f31a0dee8ff22cd65131b3d84102e6ddffa6710eea57f5775cb80b860333ace75ace2e5ee6427ca28ec8051fccf73b756f4d4bd3a230508173f22adfce331fa567a2f3b02e2eeea741dbddf1158d63fca5d56d4a6f6d4c62a9b5f03a33f3154da6ac42d33d14cd385fc16bc406a03b328bb3e2151233461194c63b3bf84be97da23257a17bcb6409d44e952719c8cf8beab9cc7848a8d2bd7c5e251b62a2a97e1f885cb943063affd88c3aa50c1a83eeb4a111d43fa203181db63378a379138730b4fd8391ae4705339d49373d75789233fe6d2243262356ef4d085bd7b1907f7ba25a8a9bb0a5465051b6b2f7180f2d8d5999c728470a5b321fe8d588182816dc82c3d118d54be614afb0dcbbabc48420bd21a806f51ffc81432782e9c275717694ed9e3d78bcbba077fb67023e60dc4bf716e1005717361820ddae14f8fb4550c8335406e06f88b9c7f74c2405b5fd4c6a294996c93d1b6a2fcfb9b954e1b479927e0fa70f1507f155294274ace469ccd464959c5887755803e6582bcec5214aeaa148650babdcc0eae6f65abaf5a0d3412e582f61805df3470ab58e1245933e9062b1d377179f55c2e0ecc99d8ffa81cf1c96075590c77f8ebea73b64f67759352ba2bf0be05ca7ac0069ca4000ec1771186a8497aa30ea6b8eb6a85b7082791b44e7fb24e4fcb6caf7ea5b193f2f06699ee1c46c8574ccd7ec092cdf439abe7dd67f9aceb61a06f34b75102612769b41dfaa57d7738aa69f03d09d532c23e8440f3925d942b59daee9d5e1d4721856ce7e6329f5c0ee39c51b655711015bfe36bbf688b3a2e35d60b69a25492a669f27e57ce8ea77efff222968957137b20fb9a0a38b769099a69ffaf483e40ab549308818ac898cce36964c54c8b7c60ddc9215eba5fdf9930df401171a7a9f33eed188f2fb56efde828dd600007ea0d136e2408a86dae03833027f3e08b611e6a69cb1907b883d4f30cd8827f87624f8860d1f5ea6077ae6e7ddb660ce8c780410d9f6017532a43bd1eece05f8bee4c8eb4f01a9c8928d515787c04577222b4ea2ee7444f68a0a4463715bdebf93ebedcc2a1b6d4055df3e4ab8466ba852fd0d6f8a61f17fa8ee760e1c0a705a5dace5279544db0c02599fc81d3533c2235e2ad2856a0a4e498c48f6ab50514959bbd57dd225dcfdb9fe4fad7d1d99e51157d9ae5a893fdb9415088d3229e457b7166e489eff66006d4338e2d5a1cc49d35e3ec96db979c78a83f52bdf4f466b8da1060c0513a3a62e6c06b97dad6162025d6c8c1009cbaee94dd8a0503737e910f8cf100eb746da013b22b8a86adc61f65a77830e0b8cf2f35a5a5af2fff9917ca3a9eb75b3860d63a5a4e8e57abc20d99fb400ceaff0bb8f59b9ad4dfa03aed33beefb0190e21968464d7da60019fa72cf0c2be4d2c4d757eb1dc8ca0292bc5e47505833cb3ea", + "blindingPrvKeys": { + "asset": "afc505d580f7ed036fbd98e10c49218a34388494d359ca56e168c00eaae60cda", + "token": "c4ebab894fc49e0155d891c07e2dc79527b839659b1e95c08e28d7f5f97b9454" + }, + "expected": { + "asset": { + "value": "2000", + "valueBlindingFactor": "4bfde56f52f525b877b661c8d1445b642a383137f5613c7144133432fb73e382", + "asset": "7e030205558cdb1179e1253f07068813d16457b7b2e90299ad80e3690f808586", + "assetBlindingFactor": "0000000000000000000000000000000000000000000000000000000000000000" + }, + "token": { + "value": "1", + "valueBlindingFactor": "16376f3bd8b9dc0ad74a3850238b52d0dc288a3d0262a77817400f9cf4280a3d", + "asset": "13965ad59bc1645d9c0797f321e81810e816bb3e310e33a013a77c5d6948d01c", + "assetBlindingFactor": "0000000000000000000000000000000000000000000000000000000000000000" + } + } + } + ], "finalValueBlindingFactor": [ { "inValues": ["20000"], @@ -78,5 +108,35 @@ ], "expected": "0100013f038e1a201a6cecf538bf712bca62e5d2f868ea0e6078c4298adf10ed61a557c78237c1899cb38aea419aef38161ffa7f82e90c0d19df8e5f37eb7d37193ce8" } + ], + "verifySurjectionProof": [ + { + "proof": "0200033e70e62dc661225a43f244ac54110cf68a855544be210442ca47e91e5580705dc0955a93a957f4a336cfec7df190c7df0c84fe86f31b51cae3ea1877304cd5a85d516a4e921dd3645783cd41fca8d519783a57dc14767946af0d4fa223d65392", + "outputAsset": "25b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a", + "outputAssetBlindingFactor": "06f82730207a7d18f56b67b8232a2183afec39080369b32b83e276017359c329", + "inputAssets": [ + "25b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a", + "ed167d1b67cf8c72fdc105e7499003a06745e2c42c7d32ed33d3c6dae06a96dd" + ], + "inputAssetBlindingFactors": [ + "11a0828ded4fa0ebcffced49d7e8118ceba3484363486d43ce04fbbb756dfbf9", + "25dde14dd92c0594a3765667ad0ba3263298426e5c5cf38148587a9cd3b2f936" + ], + "expected": true + }, + { + "proof": "0200033e8c1bb14b8bb3163102181b7f932515dad5e5ec02e6d32180c04aad77ec8f0ea12653e52cfc8f6d7854c3d671dda156a2dffa750a08f7c4a04b4644559879a46be28a1aa499f8b9068326f3a0bf764b4a8cb67d80f60d0d856118170e5787b4", + "outputAsset": "25b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a", + "outputAssetBlindingFactor": "b919547b0fe215b1cc259f97ae435d6140d6d68ab62b6ceae216af48a75ed8dd", + "inputAssets": [ + "25b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a", + "ed167d1b67cf8c72fdc105e7499003a06745e2c42c7d32ed33d3c6dae06a96dd" + ], + "inputAssetBlindingFactors": [ + "11a0828ded4fa0ebcffced49d7e8118ceba3484363486d43ce04fbbb756dfbf9", + "25dde14dd92c0594a3765667ad0ba3263298426e5c5cf38148587a9cd3b2f936" + ], + "expected": false + } ] } diff --git a/internal/elementsutil/amount.go b/internal/elementsutil/amount.go new file mode 100644 index 0000000..6d1e257 --- /dev/null +++ b/internal/elementsutil/amount.go @@ -0,0 +1,33 @@ +package elementsutil + +import ( + "bytes" + "encoding/binary" + "errors" + + "github.com/vulpemventures/go-elements/internal/bufferutil" +) + +// SatoshiToElementsValue method converts Satoshi value to Elements value +func SatoshiToElementsValue(val uint64) ([]byte, error) { + unconfPrefix := byte(1) + b := bytes.NewBuffer([]byte{}) + if err := bufferutil.BinarySerializer.PutUint64(b, binary.LittleEndian, val); err != nil { + return nil, err + } + res := append([]byte{unconfPrefix}, bufferutil.ReverseBytes(b.Bytes())...) + return res, nil +} + +// ElementsToSatoshiValue method converts Elements value to Satoshi value +func ElementsToSatoshiValue(val []byte) (uint64, error) { + if len(val) != 9 { + return 0, errors.New("invalid elements value lenght") + } + if val[0] != byte(1) { + return 0, errors.New("invalid prefix") + } + reverseValueBuffer := bufferutil.ReverseBytes(val[1:]) + d := bufferutil.NewDeserializer(bytes.NewBuffer(reverseValueBuffer)) + return d.ReadUint64() +} diff --git a/internal/elementsutil/amount_test.go b/internal/elementsutil/amount_test.go new file mode 100644 index 0000000..a3b8c90 --- /dev/null +++ b/internal/elementsutil/amount_test.go @@ -0,0 +1,28 @@ +package elementsutil + +import ( + "crypto/rand" + "math/big" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSatoshiToElementsValueRoundTrip(t *testing.T) { + bigInt, err := rand.Int(rand.Reader, big.NewInt(1000000000)) + if err != nil { + panic(err) + } + satoshi := bigInt.Uint64() + elementsValue, err := SatoshiToElementsValue(satoshi) + if !assert.NoError(t, err) { + t.FailNow() + } + + satoshiValue, err := ElementsToSatoshiValue(elementsValue) + if !assert.NoError(t, err) { + t.FailNow() + } + + assert.Equal(t, satoshi, satoshiValue) +} diff --git a/pset/blinder.go b/pset/blinder.go index 04bbb03..e3f9960 100644 --- a/pset/blinder.go +++ b/pset/blinder.go @@ -7,6 +7,7 @@ import ( "github.com/btcsuite/btcd/btcec" "github.com/vulpemventures/go-elements/confidential" + "github.com/vulpemventures/go-elements/internal/elementsutil" "github.com/vulpemventures/go-elements/transaction" ) @@ -35,6 +36,24 @@ type IssuanceBlindingPrivateKeys struct { TokenKey []byte } +func (ik IssuanceBlindingPrivateKeys) ToSlice() [][]byte { + keys := [][]byte{ik.AssetKey} + if len(ik.TokenKey) > 0 { + keys = append(keys, ik.TokenKey) + } + return keys +} + +// VerifyBlinding verifies the proofs of all the confidential outputs of the +// given transaction, with the given in/out private blinding keys. +func VerifyBlinding( + pset *Pset, + inBlindKeys, outBlindKeys [][]byte, + inIssuanceBlindKeys []IssuanceBlindingPrivateKeys, +) bool { + return verifyBlinding(pset, inBlindKeys, outBlindKeys, inIssuanceBlindKeys) +} + // NewBlinder returns a new instance of blinder, if the passed Pset struct is // in a valid form, else an error. func NewBlinder( @@ -43,10 +62,7 @@ func NewBlinder( blindingPubkeys [][]byte, issuanceBlindingPrivateKeys []IssuanceBlindingPrivateKeys, rng randomNumberGenerator, -) ( - *blinder, - error, -) { +) (*blinder, error) { if err := pset.SanityCheck(); err != nil { return nil, err } @@ -142,27 +158,13 @@ func (b *blinder) unblindInputs() ( // to the unblindedPrevOuts list, otherwise push to just add the unblided // unblinded input with 0-value blinding factors if prevout.IsConfidential() { - nonce, err := confidential.NonceHash( - prevout.Nonce, - b.blindingPrivkeys[index], - ) - unblindOutputArg := confidential.UnblindOutputArg{ - Nonce: nonce, - Rangeproof: prevout.RangeProof, - ValueCommitment: prevout.Value, - AssetCommitment: prevout.Asset, - ScriptPubkey: prevout.Script, - } - - output, err := confidential.UnblindOutput(unblindOutputArg) + output, err := confidential.UnblindOutputWithKey(prevout, b.blindingPrivkeys[index]) if err != nil { return nil, nil, err } unblindedPrevOuts = append(unblindedPrevOuts, *output) } else { - val := [confidential.ElementsUnconfidentialValueLength]byte{} - copy(val[:], prevout.Value) - satoshiValue, err := confidential.ElementsToSatoshiValue(val) + satoshiValue, err := elementsutil.ElementsToSatoshiValue(prevout.Value) if err != nil { return nil, nil, err } @@ -186,9 +188,7 @@ func (b *blinder) unblindInputs() ( return nil, nil, err } - assetAmount := [9]byte{} - copy(assetAmount[:], input.Issuance.AssetAmount) - value, _ := confidential.ElementsToSatoshiValue(assetAmount) + value, _ := elementsutil.ElementsToSatoshiValue(input.Issuance.AssetAmount) // prepare the random asset and value blinding factors in case the // issuance needs to be blinded, otherwise they're set to the 0 byte array @@ -213,9 +213,7 @@ func (b *blinder) unblindInputs() ( // to check if the input.Issuance.TokenAmount, that is encoded in the // elements format, contains more than one byte if len(input.Issuance.TokenAmount) > 1 { - tokenAmount := [9]byte{} - copy(tokenAmount[:], input.Issuance.TokenAmount) - value, err := confidential.ElementsToSatoshiValue(tokenAmount) + value, err := elementsutil.ElementsToSatoshiValue(input.Issuance.TokenAmount) if err != nil { return nil, nil, err } @@ -262,9 +260,7 @@ func (b *blinder) blindOutputs( outputValues := make([]uint64, 0) for _, output := range b.pset.UnsignedTx.Outputs { if len(output.Script) > 0 { - var val [confidential.ElementsUnconfidentialValueLength]byte - copy(val[:], output.Value) - value, err := confidential.ElementsToSatoshiValue(val) + value, err := elementsutil.ElementsToSatoshiValue(output.Value) if err != nil { return err } @@ -411,7 +407,7 @@ func (b *blinder) generateOutputBlindingFactors( outputVbfs = append(outputVbfs, rand) } - finalVbfArg := confidential.FinalValueBlindingFactorArg{ + finalVbfArgs := confidential.FinalValueBlindingFactorArgs{ InValues: inputValues, OutValues: outputValues, InGenerators: inputAbfs, @@ -420,7 +416,7 @@ func (b *blinder) generateOutputBlindingFactors( OutFactors: outputVbfs, } - finalVbf, err := confidential.FinalValueBlindingFactor(finalVbfArg) + finalVbf, err := confidential.FinalValueBlindingFactor(finalVbfArgs) if err != nil { return nil, nil, err } @@ -492,7 +488,7 @@ func (b *blinder) createBlindedOutputs( return err } - rangeProofArg := confidential.RangeProofArg{ + rangeProofArgs := confidential.RangeProofArgs{ Value: outputValue, Nonce: nonce, Asset: outputAsset, @@ -504,12 +500,12 @@ func (b *blinder) createBlindedOutputs( Exp: 0, MinBits: 52, } - rangeProof, err := confidential.RangeProof(rangeProofArg) + rangeProof, err := confidential.RangeProof(rangeProofArgs) if err != nil { return err } - surjectionProofInput := confidential.SurjectionProofArg{ + surjectionProofArgs := confidential.SurjectionProofArgs{ OutputAsset: outputAsset, OutputAssetBlindingFactor: outputAbfs[outputIndex], InputAssets: inputAgs, @@ -517,9 +513,7 @@ func (b *blinder) createBlindedOutputs( Seed: randomSeed, } - surjectionProof, ok := confidential.SurjectionProof( - surjectionProofInput, - ) + surjectionProof, ok := confidential.SurjectionProof(surjectionProofArgs) if !ok { return ErrGenerateSurjectionProof } @@ -548,17 +542,12 @@ func (b *blinder) blindAsset(index int, asset, vbf, abf []byte) error { } assetAmount := b.pset.UnsignedTx.Inputs[index].Issuance.AssetAmount - assetCommitment, err := confidential.AssetCommitment( - asset, - abf, - ) + assetCommitment, err := confidential.AssetCommitment(asset, abf) if err != nil { return err } - amount := [9]byte{} - copy(amount[:], assetAmount) - assetAmountSatoshi, err := confidential.ElementsToSatoshiValue(amount) + assetAmountSatoshi, err := elementsutil.ElementsToSatoshiValue(assetAmount) if err != nil { return err } @@ -578,7 +567,7 @@ func (b *blinder) blindAsset(index int, asset, vbf, abf []byte) error { var nonce [32]byte copy(nonce[:], b.issuanceBlindingPrivateKeys[index].AssetKey[:]) - rangeProofArg := confidential.RangeProofArg{ + rangeProofArgs := confidential.RangeProofArgs{ Value: assetAmountSatoshi, Nonce: nonce, Asset: asset, @@ -590,7 +579,7 @@ func (b *blinder) blindAsset(index int, asset, vbf, abf []byte) error { Exp: 0, MinBits: 52, } - rangeProof, err := confidential.RangeProof(rangeProofArg) + rangeProof, err := confidential.RangeProof(rangeProofArgs) if err != nil { return err } @@ -606,17 +595,12 @@ func (b *blinder) blindToken(index int, token, vbf, abf []byte) error { } tokenAmount := b.pset.UnsignedTx.Inputs[index].Issuance.TokenAmount - assetCommitment, err := confidential.AssetCommitment( - token, - abf, - ) + assetCommitment, err := confidential.AssetCommitment(token, abf) if err != nil { return err } - amount := [9]byte{} - copy(amount[:], tokenAmount) - tokenAmountSatoshi, err := confidential.ElementsToSatoshiValue(amount) + tokenAmountSatoshi, err := elementsutil.ElementsToSatoshiValue(tokenAmount) if err != nil { return err } @@ -636,7 +620,7 @@ func (b *blinder) blindToken(index int, token, vbf, abf []byte) error { var nonce [32]byte copy(nonce[:], b.issuanceBlindingPrivateKeys[index].TokenKey[:]) - rangeProofArg := confidential.RangeProofArg{ + rangeProofArgs := confidential.RangeProofArgs{ Value: tokenAmountSatoshi, Nonce: nonce, Asset: token, @@ -648,7 +632,7 @@ func (b *blinder) blindToken(index int, token, vbf, abf []byte) error { Exp: 0, MinBits: 52, } - rangeProof, err := confidential.RangeProof(rangeProofArg) + rangeProof, err := confidential.RangeProof(rangeProofArgs) if err != nil { return err } @@ -658,6 +642,123 @@ func (b *blinder) blindToken(index int, token, vbf, abf []byte) error { return nil } +func verifyBlinding( + pset *Pset, + inBlindKeys, outBlindKeys [][]byte, + inIssuanceKeys []IssuanceBlindingPrivateKeys, +) bool { + if len(pset.Inputs) != len(inBlindKeys) { + return false + } + + outCount := 0 + for _, out := range pset.UnsignedTx.Outputs { + if out.IsConfidential() { + outCount++ + } + } + if len(outBlindKeys) != outCount { + return false + } + + inAssets := make([][]byte, 0, len(pset.Inputs)) + inIssuanceAssets := make([][]byte, 0) + inAssetBlinders := make([][]byte, 0, len(pset.Inputs)) + inIssuanceAssetBlinders := make([][]byte, 0) + for i, in := range pset.Inputs { + var prevout *transaction.TxOutput + if in.WitnessUtxo != nil { + prevout = in.WitnessUtxo + } else { + prevIndex := pset.UnsignedTx.Inputs[i].Index + prevout = in.NonWitnessUtxo.Outputs[prevIndex] + } + + unblinded, err := confidential.UnblindOutputWithKey(prevout, inBlindKeys[i]) + if err != nil { + return false + } + if unblinded == nil { + inAssets = append(inAssets, prevout.Asset[1:]) + inAssetBlinders = append(inAssetBlinders, make([]byte, 32)) + } else { + inAssets = append(inAssets, unblinded.Asset) + inAssetBlinders = append(inAssetBlinders, unblinded.AssetBlindingFactor) + } + + if txIn := pset.UnsignedTx.Inputs[i]; txIn.HasIssuance() { + if txIn.HasConfidentialIssuance() { + unblinded, err := confidential.UnblindIssuance(txIn, inIssuanceKeys[i].ToSlice()) + if err != nil { + return false + } + inIssuanceAssets = append(inIssuanceAssets, unblinded.Asset.Asset) + inIssuanceAssetBlinders = append( + inIssuanceAssetBlinders, + unblinded.Asset.AssetBlindingFactor, + ) + + if len(txIn.Issuance.TokenAmount) > 0 { + inIssuanceAssets = append(inIssuanceAssets, unblinded.Token.Asset) + inIssuanceAssetBlinders = append( + inIssuanceAssetBlinders, + unblinded.Token.AssetBlindingFactor, + ) + } + } else { + iss, err := transaction.NewTxIssuanceFromInput(txIn) + if err != nil { + return false + } + asset, err := iss.GenerateAsset() + if err != nil { + return false + } + inIssuanceAssets = append(inIssuanceAssets, asset) + inIssuanceAssetBlinders = append( + inIssuanceAssetBlinders, + transaction.Zero[:], + ) + + if len(txIn.Issuance.TokenAmount) > 0 { + token, err := iss.GenerateReissuanceToken(0) + if err != nil { + return false + } + inIssuanceAssets = append(inIssuanceAssets, token) + inIssuanceAssetBlinders = append( + inIssuanceAssetBlinders, + transaction.Zero[:], + ) + } + } + } + } + + inAssets = append(inAssets, inIssuanceAssets...) + inAssetBlinders = append(inAssetBlinders, inIssuanceAssetBlinders...) + for i, out := range pset.UnsignedTx.Outputs { + if out.IsConfidential() { + unblinded, err := confidential.UnblindOutputWithKey(out, outBlindKeys[i]) + if err != nil { + return false + } + args := confidential.VerifySurjectionProofArgs{ + InputAssets: inAssets, + InputAssetBlindingFactors: inAssetBlinders, + OutputAsset: unblinded.Asset, + OutputAssetBlindingFactor: unblinded.AssetBlindingFactor, + Proof: out.SurjectionProof, + } + if !confidential.VerifySurjectionProof(args) { + return false + } + } + } + + return true +} + func generateRandomNumber() ([]byte, error) { b := make([]byte, 32) _, err := rand.Read(b) diff --git a/pset/creator_test.go b/pset/creator_test.go index 7998f36..8b00e7a 100644 --- a/pset/creator_test.go +++ b/pset/creator_test.go @@ -6,8 +6,8 @@ import ( "io/ioutil" "testing" - "github.com/vulpemventures/go-elements/confidential" "github.com/vulpemventures/go-elements/internal/bufferutil" + "github.com/vulpemventures/go-elements/internal/elementsutil" "github.com/vulpemventures/go-elements/transaction" ) @@ -34,7 +34,7 @@ func TestCreator(t *testing.T) { out := vOut.(map[string]interface{}) outAsset, _ := hex.DecodeString(out["asset"].(string)) outAsset = append([]byte{0x01}, bufferutil.ReverseBytes(outAsset)...) - outValue, _ := confidential.SatoshiToElementsValue(uint64(out["value"].(float64))) + outValue, _ := elementsutil.SatoshiToElementsValue(uint64(out["value"].(float64))) outScript, _ := hex.DecodeString(out["script"].(string)) outputs = append(outputs, transaction.NewTxOutput(outAsset, outValue[:], outScript)) } diff --git a/pset/pset_test.go b/pset/pset_test.go index 4060182..ab278c5 100644 --- a/pset/pset_test.go +++ b/pset/pset_test.go @@ -13,11 +13,10 @@ import ( "testing" "time" - "github.com/vulpemventures/go-elements/confidential" - "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/txscript" "github.com/vulpemventures/go-elements/internal/bufferutil" + "github.com/vulpemventures/go-elements/internal/elementsutil" "github.com/vulpemventures/go-elements/network" "github.com/vulpemventures/go-elements/payment" "github.com/vulpemventures/go-elements/transaction" @@ -159,12 +158,12 @@ func TestBroadcastBlindedSwapTx(t *testing.T) { "5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225", ) lbtc = append([]byte{0x01}, bufferutil.ReverseBytes(lbtc)...) - aliceToBobValue, _ := confidential.SatoshiToElementsValue(60000000) + aliceToBobValue, _ := elementsutil.SatoshiToElementsValue(60000000) aliceToBobScript := p2wpkhBob.WitnessScript aliceToBobOutput := transaction.NewTxOutput(lbtc, aliceToBobValue[:], aliceToBobScript) // Change from/to Alice changeScriptAlice := p2wpkhAlice.WitnessScript - changeValueAlice, _ := confidential.SatoshiToElementsValue(39999500) + changeValueAlice, _ := elementsutil.SatoshiToElementsValue(39999500) changeOutputAlice := transaction.NewTxOutput(lbtc, changeValueAlice[:], changeScriptAlice) // Asset hex @@ -173,7 +172,7 @@ func TestBroadcastBlindedSwapTx(t *testing.T) { //// Outputs from Bob // Asset to Alice - bobToAliceValue, _ := confidential.SatoshiToElementsValue(100000000000) + bobToAliceValue, _ := elementsutil.SatoshiToElementsValue(100000000000) bobToAliceScript := p2wpkhAlice.WitnessScript bobToAliceOutput := transaction.NewTxOutput(asset, bobToAliceValue[:], bobToAliceScript) @@ -255,41 +254,28 @@ func TestBroadcastBlindedSwapTx(t *testing.T) { t.Fatal(err) } - //blind outputs - blindingPubKeys := [][]byte{ - blindPubkeyBob.SerializeCompressed(), - blindPubkeyAlice.SerializeCompressed(), - blindPubkeyAlice.SerializeCompressed(), - } - - blindingPrivKeys := [][]byte{ + inBlindingPrvKeys := [][]byte{ blindPrivkeyAlice.Serialize(), blindPrivkeyBob.Serialize(), } + outBlindingPrvKeys := [][]byte{ + blindPrivkeyBob.Serialize(), + blindPrivkeyAlice.Serialize(), + blindPrivkeyAlice.Serialize(), + } - blinder, err := NewBlinder( + if err := blindTransaction( p, - blindingPrivKeys, - blindingPubKeys, - nil, + inBlindingPrvKeys, + outBlindingPrvKeys, nil, - ) - if err != nil { + ); err != nil { t.Fatal(err) } - for { - if err := blinder.Blind(); err != nil { - if err != ErrGenerateSurjectionProof { - t.Fatal(err) - } - continue - } - break - } // Add the unblinded outputs now, that's only the fee output in this case feeScript := []byte{} - feeValue, _ := confidential.SatoshiToElementsValue(500) + feeValue, _ := elementsutil.SatoshiToElementsValue(500) feeOutput := transaction.NewTxOutput(lbtc, feeValue[:], feeScript) updater.AddOutput(feeOutput) @@ -393,15 +379,15 @@ func TestBroadcastUnblindedTxP2PKH(t *testing.T) { ) lbtc = append([]byte{0x01}, bufferutil.ReverseBytes(lbtc)...) - receiverValue, _ := confidential.SatoshiToElementsValue(60000000) + receiverValue, _ := elementsutil.SatoshiToElementsValue(60000000) receiverScript := p2pkh.Script receiverOutput := transaction.NewTxOutput(lbtc, receiverValue[:], receiverScript) - changeValue, _ := confidential.SatoshiToElementsValue(39999500) + changeValue, _ := elementsutil.SatoshiToElementsValue(39999500) changeScript := p2pkh.Script changeOutput := transaction.NewTxOutput(lbtc, changeValue[:], changeScript) - feeValue, _ := confidential.SatoshiToElementsValue(500) + feeValue, _ := elementsutil.SatoshiToElementsValue(500) feeScript := []byte{} feeOutput := transaction.NewTxOutput(lbtc, feeValue[:], feeScript) @@ -536,15 +522,15 @@ func TestBroadcastUnblindedTxP2PKH2Inputs(t *testing.T) { ) lbtc = append([]byte{0x01}, bufferutil.ReverseBytes(lbtc)...) - receiverValue, _ := confidential.SatoshiToElementsValue(160000000) + receiverValue, _ := elementsutil.SatoshiToElementsValue(160000000) receiverScript := p2pkh.Script receiverOutput := transaction.NewTxOutput(lbtc, receiverValue[:], receiverScript) - changeValue, _ := confidential.SatoshiToElementsValue(39999500) + changeValue, _ := elementsutil.SatoshiToElementsValue(39999500) changeScript := p2pkh.Script changeOutput := transaction.NewTxOutput(lbtc, changeValue[:], changeScript) - feeValue, _ := confidential.SatoshiToElementsValue(500) + feeValue, _ := elementsutil.SatoshiToElementsValue(500) feeScript := []byte{} feeOutput := transaction.NewTxOutput(lbtc, feeValue[:], feeScript) @@ -705,16 +691,16 @@ func TestBroadcastUnblindedTx(t *testing.T) { ) lbtc = append([]byte{0x01}, bufferutil.ReverseBytes(lbtc)...) - receiverValue, _ := confidential.SatoshiToElementsValue(60000000) + receiverValue, _ := elementsutil.SatoshiToElementsValue(60000000) receiverScript, _ := hex.DecodeString("76a91439397080b51ef22c59bd7469afacffbeec0da12e88ac") receiverOutput := transaction.NewTxOutput(lbtc, receiverValue[:], receiverScript) changeScript := p2wpkh.WitnessScript - changeValue, _ := confidential.SatoshiToElementsValue(39999500) + changeValue, _ := elementsutil.SatoshiToElementsValue(39999500) changeOutput := transaction.NewTxOutput(lbtc, changeValue[:], changeScript) feeScript := []byte{} - feeValue, _ := confidential.SatoshiToElementsValue(500) + feeValue, _ := elementsutil.SatoshiToElementsValue(500) feeOutput := transaction.NewTxOutput(lbtc, feeValue[:], feeScript) // Create a new pset. @@ -735,7 +721,7 @@ func TestBroadcastUnblindedTx(t *testing.T) { if err != nil { t.Fatal(err) } - witValue, _ := confidential.SatoshiToElementsValue(uint64(utxos[0]["value"].(float64))) + witValue, _ := elementsutil.SatoshiToElementsValue(uint64(utxos[0]["value"].(float64))) witnessUtxo := transaction.NewTxOutput(lbtc, witValue[:], p2wpkh.WitnessScript) updater.AddInWitnessUtxo(witnessUtxo, 0) @@ -836,11 +822,11 @@ func TestBroadcastUnblindedIssuanceTx(t *testing.T) { lbtc = append([]byte{0x01}, bufferutil.ReverseBytes(lbtc)...) changeScript := p2wpkh.WitnessScript - changeValue, _ := confidential.SatoshiToElementsValue(99999500) + changeValue, _ := elementsutil.SatoshiToElementsValue(99999500) changeOutput := transaction.NewTxOutput(lbtc, changeValue[:], changeScript) feeScript := []byte{} - feeValue, _ := confidential.SatoshiToElementsValue(500) + feeValue, _ := elementsutil.SatoshiToElementsValue(500) feeOutput := transaction.NewTxOutput(lbtc, feeValue[:], feeScript) // Create a new pset. @@ -883,7 +869,7 @@ func TestBroadcastUnblindedIssuanceTx(t *testing.T) { if err != nil { t.Fatal(err) } - witValue, _ := confidential.SatoshiToElementsValue(uint64(utxos[0]["value"].(float64))) + witValue, _ := elementsutil.SatoshiToElementsValue(uint64(utxos[0]["value"].(float64))) witnessUtxo := transaction.NewTxOutput(lbtc, witValue[:], p2wpkh.WitnessScript) err = updater.AddInWitnessUtxo(witnessUtxo, 0) if err != nil { @@ -973,12 +959,12 @@ func TestBroadcastBlindedTx(t *testing.T) { "5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225", ) lbtc = append([]byte{0x01}, bufferutil.ReverseBytes(lbtc)...) - receiverValue, _ := confidential.SatoshiToElementsValue(60000000) + receiverValue, _ := elementsutil.SatoshiToElementsValue(60000000) receiverScript, _ := hex.DecodeString("76a91439397080b51ef22c59bd7469afacffbeec0da12e88ac") receiverOutput := transaction.NewTxOutput(lbtc, receiverValue[:], receiverScript) changeScript := p2wpkh.WitnessScript - changeValue, _ := confidential.SatoshiToElementsValue(39999500) + changeValue, _ := elementsutil.SatoshiToElementsValue(39999500) changeOutput := transaction.NewTxOutput(lbtc, changeValue[:], changeScript) // Create a new pset with all the outputs that need to be blinded first @@ -999,7 +985,7 @@ func TestBroadcastBlindedTx(t *testing.T) { if err != nil { t.Fatal(err) } - witValue, _ := confidential.SatoshiToElementsValue(uint64(utxos[0]["value"].(float64))) + witValue, _ := elementsutil.SatoshiToElementsValue(uint64(utxos[0]["value"].(float64))) witnessUtxo := transaction.NewTxOutput(lbtc, witValue[:], p2wpkh.WitnessScript) err = updater.AddInWitnessUtxo(witnessUtxo, 0) if err != nil { @@ -1007,45 +993,28 @@ func TestBroadcastBlindedTx(t *testing.T) { } //blind outputs - blindingPrivKeys := [][]byte{{}} - - blindingPubKeys := make([][]byte, 0) - pk, err := btcec.NewPrivateKey(btcec.S256()) - if err != nil { - t.Fatal(err) - } - blindingpubkey := pk.PubKey().SerializeCompressed() - blindingPubKeys = append(blindingPubKeys, blindingpubkey) - pk1, err := btcec.NewPrivateKey(btcec.S256()) - if err != nil { - t.Fatal(err) + inBlindingPrvKeys := [][]byte{{}} + outBlindingPrvKeys := make([][]byte, 2) + for i := range outBlindingPrvKeys { + pk, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Fatal(err) + } + outBlindingPrvKeys[i] = pk.Serialize() } - blindingpubkey1 := pk1.PubKey().SerializeCompressed() - blindingPubKeys = append(blindingPubKeys, blindingpubkey1) - blinder, err := NewBlinder( + if err := blindTransaction( p, - blindingPrivKeys, - blindingPubKeys, + inBlindingPrvKeys, + outBlindingPrvKeys, nil, - nil, - ) - if err != nil { + ); err != nil { t.Fatal(err) } - for { - if err := blinder.Blind(); err != nil { - if err != ErrGenerateSurjectionProof { - t.Fatal(err) - } - continue - } - break - } // Add the unblinded outputs now, that's only the fee output in this case feeScript := []byte{} - feeValue, _ := confidential.SatoshiToElementsValue(500) + feeValue, _ := elementsutil.SatoshiToElementsValue(500) feeOutput := transaction.NewTxOutput(lbtc, feeValue[:], feeScript) updater.AddOutput(feeOutput) @@ -1138,12 +1107,12 @@ func TestBroadcastBlindedTxWithBlindedInput(t *testing.T) { "5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225", ) lbtc = append([]byte{0x01}, bufferutil.ReverseBytes(lbtc)...) - receiverValue, _ := confidential.SatoshiToElementsValue(60000000) + receiverValue, _ := elementsutil.SatoshiToElementsValue(60000000) receiverScript, _ := hex.DecodeString("76a91439397080b51ef22c59bd7469afacffbeec0da12e88ac") receiverOutput := transaction.NewTxOutput(lbtc, receiverValue[:], receiverScript) changeScript := p2wpkh.WitnessScript - changeValue, _ := confidential.SatoshiToElementsValue(39999500) + changeValue, _ := elementsutil.SatoshiToElementsValue(39999500) changeOutput := transaction.NewTxOutput(lbtc, changeValue[:], changeScript) // Create a new pset. @@ -1196,46 +1165,27 @@ func TestBroadcastBlindedTxWithBlindedInput(t *testing.T) { t.Fatal(err) } //blind outputs - blindingPrivKeys := [][]byte{blindingPrivateKey.Serialize()} - - blindingPubKeys := make([][]byte, 0) - pk, err := btcec.NewPrivateKey(btcec.S256()) - if err != nil { - t.Fatal(err) - } - blindingpubkey := pk.PubKey().SerializeCompressed() - blindingPubKeys = append(blindingPubKeys, blindingpubkey) - pk1, err := btcec.NewPrivateKey(btcec.S256()) - if err != nil { - t.Fatal(err) + inBlindingPrvKeys := [][]byte{blindingPrivateKey.Serialize()} + outBlindingPrvKeys := make([][]byte, 2) + for i := range outBlindingPrvKeys { + pk, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Fatal(err) + } + outBlindingPrvKeys[i] = pk.Serialize() } - blindingpubkey1 := pk1.PubKey().SerializeCompressed() - blindingPubKeys = append(blindingPubKeys, blindingpubkey1) - blinder, err := NewBlinder( + if err := blindTransaction( p, - blindingPrivKeys, - blindingPubKeys, - nil, + inBlindingPrvKeys, + outBlindingPrvKeys, nil, - ) - if err != nil { + ); err != nil { t.Fatal(err) } - for { - if err := blinder.Blind(); err != nil { - if err != ErrGenerateSurjectionProof { - t.Fatal(err) - } - fmt.Println(err) - continue - } - break - } - feeScript := []byte{} - feeValue, _ := confidential.SatoshiToElementsValue(500) + feeValue, _ := elementsutil.SatoshiToElementsValue(500) feeOutput := transaction.NewTxOutput(lbtc, feeValue[:], feeScript) updater.AddOutput(feeOutput) @@ -1370,7 +1320,7 @@ func TestBroadcastIssuanceTxWithBlindedOutput(t *testing.T) { if err != nil { t.Fatal(err) } - witValue, _ := confidential.SatoshiToElementsValue(uint64(utxos[0]["value"].(float64))) + witValue, _ := elementsutil.SatoshiToElementsValue(uint64(utxos[0]["value"].(float64))) witnessUtxo := transaction.NewTxOutput(lbtc, witValue[:], p2wpkh.WitnessScript) err = updater.AddInWitnessUtxo(witnessUtxo, 0) if err != nil { @@ -1379,64 +1329,36 @@ func TestBroadcastIssuanceTxWithBlindedOutput(t *testing.T) { // Add change and fees changeScript := p2wpkh.WitnessScript - changeValue, _ := confidential.SatoshiToElementsValue(99996000) + changeValue, _ := elementsutil.SatoshiToElementsValue(99996000) changeOutput := transaction.NewTxOutput(lbtc, changeValue[:], changeScript) updater.AddOutput(changeOutput) //blind outputs - blindingPrivKeys := [][]byte{{}} - blindingPubKeys := make([][]byte, 0) - - pk, err := btcec.NewPrivateKey(btcec.S256()) - if err != nil { - t.Fatal(err) - } - // blinding pubkey for asset output - blindingpubkey := pk.PubKey().SerializeCompressed() - blindingPubKeys = append(blindingPubKeys, blindingpubkey) - - // blinding pubkey for token output - blindingPubKeys = append(blindingPubKeys, blindingpubkey) - - // blinding pubkey for change output - pk1, err := btcec.NewPrivateKey(btcec.S256()) - if err != nil { - t.Fatal(err) + inBlindingPrvKeys := [][]byte{{}} + outBlindingPrvKeys := make([][]byte, 2) + for i := range outBlindingPrvKeys { + pk, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Fatal(err) + } + outBlindingPrvKeys[i] = pk.Serialize() } - blindingpubkey1 := pk1.PubKey().SerializeCompressed() - blindingPubKeys = append(blindingPubKeys, blindingpubkey1) - - issuanceBlindingPrivateKeys := make([]IssuanceBlindingPrivateKeys, 0) - issuanceBlindingPrivateKeys = append( - issuanceBlindingPrivateKeys, - IssuanceBlindingPrivateKeys{ - AssetKey: pk.Serialize(), - TokenKey: pk1.Serialize(), - }, + outBlindingPrvKeys = append( + [][]byte{outBlindingPrvKeys[0]}, + outBlindingPrvKeys..., ) - blinder, err := NewBlinder( + if err := blindTransaction( p, - blindingPrivKeys, - blindingPubKeys, - nil, + inBlindingPrvKeys, + outBlindingPrvKeys, nil, - ) - if err != nil { + ); err != nil { t.Fatal(err) } - for { - if err := blinder.Blind(); err != nil { - if err != ErrGenerateSurjectionProof { - t.Fatal(err) - } - continue - } - break - } feeScript := []byte{} - feeValue, _ := confidential.SatoshiToElementsValue(4000) + feeValue, _ := elementsutil.SatoshiToElementsValue(4000) feeOutput := transaction.NewTxOutput(lbtc, feeValue[:], feeScript) updater.AddOutput(feeOutput) @@ -1558,7 +1480,7 @@ func TestBroadcastBlindedIssuanceTx(t *testing.T) { if err != nil { t.Fatal(err) } - witValue, _ := confidential.SatoshiToElementsValue(uint64(utxos[0]["value"].(float64))) + witValue, _ := elementsutil.SatoshiToElementsValue(uint64(utxos[0]["value"].(float64))) witnessUtxo := transaction.NewTxOutput(lbtc, witValue[:], p2wpkh.WitnessScript) err = updater.AddInWitnessUtxo(witnessUtxo, 0) if err != nil { @@ -1567,65 +1489,44 @@ func TestBroadcastBlindedIssuanceTx(t *testing.T) { // Add change and fees changeScript := p2wpkh.WitnessScript - changeValue, _ := confidential.SatoshiToElementsValue(99996000) + changeValue, _ := elementsutil.SatoshiToElementsValue(99996000) changeOutput := transaction.NewTxOutput(lbtc, changeValue[:], changeScript) updater.AddOutput(changeOutput) //blind outputs - blindingPrivKeys := [][]byte{{}} - blindingPubKeys := make([][]byte, 0) - - pk, err := btcec.NewPrivateKey(btcec.S256()) - if err != nil { - t.Fatal(err) - } - // blinding pubkey for asset output - blindingpubkey := pk.PubKey().SerializeCompressed() - blindingPubKeys = append(blindingPubKeys, blindingpubkey) - - // blinding pubkey for token output - blindingPubKeys = append(blindingPubKeys, blindingpubkey) - - // blinding pubkey for change output - pk1, err := btcec.NewPrivateKey(btcec.S256()) - if err != nil { - t.Fatal(err) + inBlindingPrvKeys := [][]byte{{}} + outBlindingPrvKeys := make([][]byte, 2) + for i := range outBlindingPrvKeys { + pk, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Fatal(err) + } + fmt.Println(i, hex.EncodeToString(pk.Serialize())) + outBlindingPrvKeys[i] = pk.Serialize() } - blindingpubkey1 := pk1.PubKey().SerializeCompressed() - blindingPubKeys = append(blindingPubKeys, blindingpubkey1) + outBlindingPrvKeys = append( + [][]byte{outBlindingPrvKeys[0]}, + outBlindingPrvKeys..., + ) - issuanceBlindingPrivateKeys := make([]IssuanceBlindingPrivateKeys, 0) - issuanceBlindingPrivateKeys = append( - issuanceBlindingPrivateKeys, + issuanceBlindingPrvKeys := []IssuanceBlindingPrivateKeys{ IssuanceBlindingPrivateKeys{ - AssetKey: pk.Serialize(), - TokenKey: pk1.Serialize(), + AssetKey: outBlindingPrvKeys[1], + TokenKey: outBlindingPrvKeys[2], }, - ) + } - blinder, err := NewBlinder( + if err := blindTransaction( p, - blindingPrivKeys, - blindingPubKeys, - issuanceBlindingPrivateKeys, - nil, - ) - if err != nil { + inBlindingPrvKeys, + outBlindingPrvKeys, + issuanceBlindingPrvKeys, + ); err != nil { t.Fatal(err) } - for { - if err := blinder.Blind(); err != nil { - if err != ErrGenerateSurjectionProof { - t.Fatal(err) - } - fmt.Println(err) - continue - } - break - } feeScript := []byte{} - feeValue, _ := confidential.SatoshiToElementsValue(4000) + feeValue, _ := elementsutil.SatoshiToElementsValue(4000) feeOutput := transaction.NewTxOutput(lbtc, feeValue[:], feeScript) updater.AddOutput(feeOutput) @@ -1670,10 +1571,58 @@ func TestBroadcastBlindedIssuanceTx(t *testing.T) { t.Fatal(err) } - _, err = broadcast(txHex) + txId, err := broadcast(txHex) if err != nil { t.Fatal(err) } + fmt.Println(txId) +} + +func blindTransaction( + p *Pset, + inBlindKeys, outBlindKeys [][]byte, + issuanceBlindKeys []IssuanceBlindingPrivateKeys, +) error { + outBlindPubKeys := make([][]byte, 0, len(outBlindKeys)) + for _, k := range outBlindKeys { + _, pubkey := btcec.PrivKeyFromBytes(btcec.S256(), k) + outBlindPubKeys = append(outBlindPubKeys, pubkey.SerializeCompressed()) + } + + psetBase64, err := p.ToBase64() + if err != nil { + return err + } + + for { + ptx, _ := NewPsetFromBase64(psetBase64) + blinder, err := NewBlinder( + ptx, + inBlindKeys, + outBlindPubKeys, + issuanceBlindKeys, + nil, + ) + if err != nil { + return err + } + + for { + if err := blinder.Blind(); err != nil { + if err != ErrGenerateSurjectionProof { + return err + } + continue + } + break + } + + if VerifyBlinding(ptx, inBlindKeys, outBlindKeys, issuanceBlindKeys) { + *p = *ptx + break + } + } + return nil } func faucet(address string) (string, error) { diff --git a/pset/updater_test.go b/pset/updater_test.go index 5a3d3dc..df1a929 100644 --- a/pset/updater_test.go +++ b/pset/updater_test.go @@ -8,8 +8,8 @@ import ( "github.com/btcsuite/btcd/txscript" "github.com/stretchr/testify/assert" - "github.com/vulpemventures/go-elements/confidential" "github.com/vulpemventures/go-elements/internal/bufferutil" + "github.com/vulpemventures/go-elements/internal/elementsutil" "github.com/vulpemventures/go-elements/transaction" ) @@ -41,7 +41,7 @@ func TestUpdater(t *testing.T) { asset, _ := hex.DecodeString(wu["asset"].(string)) asset = append([]byte{0x01}, bufferutil.ReverseBytes(asset)...) script, _ := hex.DecodeString(wu["script"].(string)) - value, _ := confidential.SatoshiToElementsValue(uint64(wu["value"].(float64))) + value, _ := elementsutil.SatoshiToElementsValue(uint64(wu["value"].(float64))) utxo := transaction.NewTxOutput(asset, value[:], script) updater.AddInWitnessUtxo(utxo, inIndex) redeemScript, _ := hex.DecodeString(in["redeemScript"].(string)) diff --git a/transaction/issuance.go b/transaction/issuance.go index 4fa2c41..01000c6 100644 --- a/transaction/issuance.go +++ b/transaction/issuance.go @@ -1,14 +1,15 @@ package transaction import ( + "bytes" "encoding/json" "errors" "math" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/vulpemventures/fastsha256" - "github.com/vulpemventures/go-elements/confidential" "github.com/vulpemventures/go-elements/internal/bufferutil" + "github.com/vulpemventures/go-elements/internal/elementsutil" ) // IssuanceEntity defines one of the fields of the issuance contract @@ -33,6 +34,11 @@ type TxIssuance struct { TokenAmount []byte } +// IsReissuance returns whether the issuance is an asset re-issuance +func (issuance *TxIssuance) IsReissuance() bool { + return !bytes.Equal(issuance.AssetBlindingNonce, Zero[:]) +} + // TxIssuanceExtended adds fields to the issuance type that are not encoded in // the transaction type TxIssuanceExtended struct { @@ -41,11 +47,32 @@ type TxIssuanceExtended struct { ContractHash []byte } -// NewTxIssuance returns a new issuance instance from contract hash +// NewTxIssuanceFromInput returns the extended issuance for the given input +func NewTxIssuanceFromInput(in *TxInput) (*TxIssuanceExtended, error) { + if in.Issuance.IsReissuance() { + return NewTxIssuanceFromEntropy(in.Issuance.AssetEntropy), nil + } + + iss := NewTxIssuanceFromContractHash(in.Issuance.AssetEntropy) + if err := iss.GenerateEntropy(in.Hash, in.Index); err != nil { + return nil, err + } + return iss, nil +} + +// NewTxIssuanceFromContractHash returns a new issuance instance from contract hash func NewTxIssuanceFromContractHash(contractHash []byte) *TxIssuanceExtended { return &TxIssuanceExtended{ContractHash: contractHash} } +// NewTxIssuanceFromEntropy returns a new issuance instance from entropy +func NewTxIssuanceFromEntropy(entropy []byte) *TxIssuanceExtended { + issuance := &TxIssuanceExtended{ + TxIssuance: TxIssuance{AssetEntropy: entropy}, + } + return issuance +} + // NewTxIssuance returns a new issuance instance func NewTxIssuance( assetAmount uint64, @@ -172,7 +199,7 @@ func (issuance *TxIssuanceExtended) GenerateReissuanceToken(flag uint) ([]byte, func toConfidentialAssetAmount(assetAmount uint64, precision uint) ([]byte, error) { amount := assetAmount * uint64(math.Pow10(int(precision))) - confAmount, err := confidential.SatoshiToElementsValue(amount) + confAmount, err := elementsutil.SatoshiToElementsValue(amount) if err != nil { return nil, err } @@ -185,7 +212,7 @@ func toConfidentialTokenAmount(tokenAmount uint64, precision uint) ([]byte, erro } amount := tokenAmount * uint64(math.Pow10(int(precision))) - confAmount, err := confidential.SatoshiToElementsValue(amount) + confAmount, err := elementsutil.SatoshiToElementsValue(amount) if err != nil { return nil, err } diff --git a/transaction/issuance_test.go b/transaction/issuance_test.go index 5d166ff..efc2d7f 100644 --- a/transaction/issuance_test.go +++ b/transaction/issuance_test.go @@ -7,8 +7,8 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/vulpemventures/go-elements/confidential" "github.com/vulpemventures/go-elements/internal/bufferutil" + "github.com/vulpemventures/go-elements/internal/elementsutil" ) func TestIssuanceGeneration(t *testing.T) { @@ -48,14 +48,11 @@ func TestIssuanceGeneration(t *testing.T) { t.FailNow() } - amount := [9]byte{} - copy(amount[:], issuance.TxIssuance.AssetAmount) - resAssetAmount, _ := confidential.ElementsToSatoshiValue( - amount, + resAssetAmount, _ := elementsutil.ElementsToSatoshiValue( + issuance.TxIssuance.AssetAmount, ) - copy(amount[:], issuance.TxIssuance.TokenAmount) - resTokenAmount, _ := confidential.ElementsToSatoshiValue( - amount, + resTokenAmount, _ := elementsutil.ElementsToSatoshiValue( + issuance.TxIssuance.TokenAmount, ) assert.Equal(t, uint64(v["expectedAssetAmount"].(float64)), resAssetAmount) assert.Equal(t, uint64(v["expectedTokenAmount"].(float64)), resTokenAmount) diff --git a/transaction/transaction.go b/transaction/transaction.go index 0c38c9d..57942b2 100644 --- a/transaction/transaction.go +++ b/transaction/transaction.go @@ -87,6 +87,11 @@ func (in *TxInput) HasIssuance() bool { return in.Issuance != nil } +// HasConfidentialIssuance returns whether the input contains a blinded issuance +func (in *TxInput) HasConfidentialIssuance() bool { + return in.HasIssuance() && len(in.IssuanceRangeProof) > 0 +} + // TxWitness defines the witness for a TxIn. A witness is to be interpreted as // a slice of byte slices, or a stack with one or many elements. type TxWitness [][]byte diff --git a/transaction/transaction_test.go b/transaction/transaction_test.go index e43cc3a..b61168e 100644 --- a/transaction/transaction_test.go +++ b/transaction/transaction_test.go @@ -3,12 +3,12 @@ package transaction import ( "encoding/hex" "encoding/json" - "github.com/vulpemventures/go-elements/confidential" "io/ioutil" "reflect" "testing" "github.com/btcsuite/btcd/txscript" + "github.com/vulpemventures/go-elements/internal/elementsutil" ) func TestRoundTrip(t *testing.T) { @@ -198,7 +198,7 @@ func TestHashForWitnessV0(t *testing.T) { inIndex := int(testVector["inIndex"].(float64)) script, _ := hex.DecodeString(testVector["script"].(string)) hashType := txscript.SigHashType(testVector["hashType"].(float64)) - value, _ := confidential.SatoshiToElementsValue(uint64(testVector["amount"].(float64))) + value, _ := elementsutil.SatoshiToElementsValue(uint64(testVector["amount"].(float64))) hash := tx.HashForWitnessV0(inIndex, script, value[:], hashType) expectedHash := testVector["expectedHash"].(string)