\r
```java\r
\r
-//utxo json to input public Transaction.AnnotatedInput btmUtxoToInput() { \r
+AnnotatedBaseInput\r
String utxoJson = "{\n" + \r
" \"id\": \"cf2f5c7340490d33d535a680dc8d95bb66fcccbf1045706484621cc067b982ae\",\n" + \r
" \"amount\": 70000000,\n" + \r
* Input:\r
* s[0]+256*s[1]+...+256^63*s[63] = s\r
* <p>\r
- * Output:\r
+ * OutputEntry:\r
* s[0]+256*s[1]+...+256^31*s[31] = s mod l\r
* where l = 2^252 + 27742317777372353535851937790883648493.\r
* Overwrites s in place.\r
* b[0]+256*b[1]+...+256^31*b[31] = b\r
* c[0]+256*c[1]+...+256^31*c[31] = c\r
* <p>\r
- * Output:\r
+ * OutputEntry:\r
* s[0]+256*s[1]+...+256^31*s[31] = (ab+c) mod l\r
* where l = 2^252 + 27742317777372353535851937790883648493.\r
*/\r
--- /dev/null
+package io.bytom.api;
+
+public enum BIPProtocol {
+
+ BIP32,
+ BIP44
+}
--- /dev/null
+package io.bytom.api;
+
+import io.bytom.common.Utils;
+import io.bytom.types.*;
+import java.io.IOException;
+import java.util.Map;
+
+public abstract class BaseInput {
+
+ public static final int ISSUANCE_INPUT_TYPE = 0;
+ public static final int SPEND_INPUT_TYPE = 1;
+
+ private String inputID;
+
+ /**
+ * The number of units of the asset being issued or spent.
+ */
+ private long amount;
+
+ /**
+ * The id of the asset being issued or spent.
+ */
+ private String assetId;
+
+ /**
+ * The program which must be satisfied to transfer this output.
+ */
+ private String program;
+
+ private int vmVersion;
+
+ private int keyIndex;
+
+ private WitnessComponent witnessComponent = new WitnessComponent();
+
+ public BaseInput() {
+ this.vmVersion = 1;
+ }
+
+ public abstract InputEntry convertInputEntry(Map<Hash, Entry> entryMap, int index);
+
+ public abstract byte[] serializeInput() throws IOException;
+
+ public abstract void buildWitness(String txID) throws Exception;
+
+ @Override
+ public String toString() {
+ return Utils.serializer.toJson(this);
+ }
+
+
+ public AssetAmount getAssetAmount() {
+ return new AssetAmount(new AssetID(this.assetId), this.amount);
+ }
+
+ public void setRootPrivateKey(String prvKey) {
+ witnessComponent.setRootPrivateKey(prvKey);
+ }
+
+ public String getInputID() {
+ return inputID;
+ }
+
+ public int getKeyIndex() {
+ return keyIndex;
+ }
+
+ public int getVmVersion() {
+ return vmVersion;
+ }
+
+ public String getProgram() {
+ return program;
+ }
+
+ public String getAssetId() {
+ return assetId;
+ }
+
+ public long getAmount() {
+ return amount;
+ }
+
+ public void setInputID(String inputID) {
+ this.inputID = inputID;
+ }
+
+ public void setAmount(long amount) {
+ this.amount = amount;
+ }
+
+ public void setAssetId(String assetId) {
+ this.assetId = assetId;
+ }
+
+ public void setProgram(String program) {
+ this.program = program;
+ }
+
+ public void setKeyIndex(int keyIndex) {
+ this.keyIndex = keyIndex;
+ }
+
+ public WitnessComponent getWitnessComponent() {
+ return witnessComponent;
+ }
+}
--- /dev/null
+package io.bytom.api;
+
+import io.bytom.common.DerivePrivateKey;
+import io.bytom.common.ExpandedPrivateKey;
+import io.bytom.common.Utils;
+import io.bytom.types.*;
+import io.bytom.util.SHA3Util;
+import org.bouncycastle.util.encoders.Hex;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.SecureRandom;
+import java.util.Map;
+
+public class IssuanceInput extends BaseInput {
+
+ private String nonce;
+
+ private String assetDefinition;
+
+ public IssuanceInput() {}
+
+ @Override
+ public InputEntry convertInputEntry(Map<Hash, Entry> entryMap, int index) {
+ if (this.nonce == null) {
+ SecureRandom sr = new SecureRandom();
+ byte[] randBytes = new byte[8];
+ sr.nextBytes(randBytes);
+ this.nonce = Hex.toHexString(randBytes);
+ }
+
+ Hash nonceHash = new Hash(SHA3Util.hashSha256(Hex.decode(this.nonce)));
+ Hash assetDefHash = new Hash(this.assetDefinition);
+ AssetAmount value = this.getAssetAmount();
+
+ Issue issuance = new Issue(nonceHash, value, index);
+ Program pro = new Program(this.getVmVersion(), Hex.decode(this.getProgram()));
+ issuance.assetDefinition = new AssetDefinition(assetDefHash, pro);
+ return issuance;
+ }
+
+ @Override
+ public byte[] serializeInput() throws IOException {
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ //assetVersion
+ Utils.writeVarint(1, stream);
+ //写入 type nonce assetId amount
+ ByteArrayOutputStream issueInfo = new ByteArrayOutputStream();
+ //写入 input.type==00 issue
+ Utils.writeVarint(ISSUANCE_INPUT_TYPE, issueInfo);
+ //写入 8个字节的随机数
+ Utils.writeVarStr(Hex.decode(nonce), issueInfo);
+ issueInfo.write(Hex.decode(getAssetId()));
+ Utils.writeVarint(getAmount(), issueInfo);
+ stream.write(issueInfo.toByteArray().length);
+ stream.write(issueInfo.toByteArray());
+
+ ByteArrayOutputStream issueInfo1 = new ByteArrayOutputStream();
+ //未知
+ Utils.writeVarint(1, issueInfo1);
+ //写入assetDefine
+ Utils.writeVarStr(Hex.decode(assetDefinition), issueInfo1);
+ //vm.version
+ Utils.writeVarint(1, issueInfo1);
+ //controlProgram
+ Utils.writeVarStr(Hex.decode(getProgram()), issueInfo1);
+
+ //inputWitness
+ if (null != getWitnessComponent()) {
+ ByteArrayOutputStream witnessStream = new ByteArrayOutputStream();
+ //arguments
+ int witnessSize = getWitnessComponent().size();
+ //arguments的length
+ Utils.writeVarint(witnessSize, witnessStream);
+ for (int i = 0; i < witnessSize; i++) {
+ String witness = getWitnessComponent().getWitness(i);
+ Utils.writeVarStr(Hex.decode(witness), witnessStream);
+ }
+ issueInfo1.write(witnessStream.toByteArray());
+ }
+ stream.write(issueInfo1.toByteArray().length - 1);
+ stream.write(issueInfo1.toByteArray());
+ return stream.toByteArray();
+ }
+
+ @Override
+ public void buildWitness(String txID) throws Exception {
+ String rootPrivateKey = getWitnessComponent().getRootPrivateKey();
+ byte[] childPrivateKey = DerivePrivateKey.bip32derivePrvKey(rootPrivateKey, getKeyIndex(), TransactionSigner.AssetKeySpace);
+
+ byte[] message = Utils.hashFn(Hex.decode(getInputID()), Hex.decode(txID));
+ byte[] expandedPrivateKey = ExpandedPrivateKey.expandedPrivateKey(childPrivateKey);
+ byte[] sig = Signer.ed25519InnerSign(expandedPrivateKey, message);
+
+ getWitnessComponent().appendWitness(Hex.toHexString(sig));
+ }
+
+ public void setNonce(String nonce) {
+ this.nonce = nonce;
+ }
+
+ public void setAssetDefinition(String assetDefinition) {
+ this.assetDefinition = assetDefinition;
+ }
+}
+++ /dev/null
-package io.bytom.api;\r
-\r
-import io.bytom.types.*;\r
-import io.bytom.util.SHA3Util;\r
-import org.bouncycastle.util.encoders.Hex;\r
-\r
-import java.security.SecureRandom;\r
-import java.util.ArrayList;\r
-import java.util.HashMap;\r
-import java.util.List;\r
-import java.util.Map;\r
-\r
-public class MapTransaction {\r
-\r
- public static Transaction mapTx(Transaction tx) {\r
- Map<Hash, Entry> entryMap = new HashMap<>();\r
- ValueSource[] muxSources = new ValueSource[tx.inputs.size()];\r
- List<Spend> spends = new ArrayList<>();\r
- List<Issue> issuances = new ArrayList<>();\r
- try {\r
- for (int i = 0; i < tx.inputs.size(); i++) {\r
- Transaction.AnnotatedInput input = tx.inputs.get(i);\r
- switch (input.type) {\r
- case 1: {\r
- Program pro = new Program(input.type, Hex.decode(input.controlProgram));\r
- AssetID assetID = new AssetID(input.assetId);\r
- AssetAmount assetAmount = new AssetAmount(assetID, input.amount);\r
- Hash sourceID = new Hash(input.sourceId);\r
- ValueSource src = new ValueSource(sourceID, assetAmount, input.sourcePosition);\r
-\r
- Output prevout = new Output(src, pro, 0);\r
- Hash prevoutID = addEntry(entryMap, prevout);\r
- Spend spend = new Spend(prevoutID, i);\r
- Hash spendID = addEntry(entryMap, spend);\r
- input.inputID = spendID.toString();\r
-\r
- muxSources[i] = new ValueSource(spendID, assetAmount, 0);\r
- spends.add(spend);\r
- break;\r
- }\r
- case 0: {\r
- if (input.nonce == null) {\r
- SecureRandom sr = new SecureRandom();\r
- byte[] randBytes = new byte[8];\r
- sr.nextBytes(randBytes);\r
- input.nonce = Hex.toHexString(randBytes);\r
- }\r
- Hash nonceHash = new Hash(SHA3Util.hashSha256(Hex.decode(input.nonce)));\r
- Hash assetDefHash = new Hash(input.assetDefinition);\r
- AssetID assetID = new AssetID(input.assetId);\r
- AssetAmount value = new AssetAmount(assetID, input.amount);\r
-\r
- Issue issuance = new Issue(nonceHash, value, i);\r
- Program pro = new Program(input.type, Hex.decode(input.controlProgram));\r
- issuance.assetDefinition = new AssetDefinition(assetDefHash, pro);\r
- Hash issuanceID = addEntry(entryMap, issuance);\r
- input.inputID = issuanceID.toString();\r
- muxSources[i] = new ValueSource(issuanceID, value, 0);\r
- issuances.add(issuance);\r
- break;\r
- }\r
- }\r
- }\r
- Mux mux = new Mux(muxSources, new Program(1, new byte[]{0x51}));\r
- Hash muxID = addEntry(entryMap, mux);\r
- for (Spend spend : spends) {\r
- Output spendOutput = (Output) entryMap.get(spend.spentOutputID);\r
- spend.setDestination(muxID, spendOutput.source.value, spend.ordinal);\r
- }\r
- for (Issue issue : issuances) {\r
- issue.setDestination(muxID, issue.assetAmount, issue.ordinal);\r
- }\r
-\r
- List<Hash> resultIDList = new ArrayList<>();\r
- for (int i = 0; i < tx.outputs.size(); i++) {\r
- Transaction.AnnotatedOutput output = tx.outputs.get(i);\r
-\r
- AssetAmount amount = new AssetAmount(new AssetID(output.assetId), output.amount);\r
- ValueSource src = new ValueSource(muxID, amount, i);\r
- Hash resultID;\r
- if (output.controlProgram.startsWith("6a")&&output.controlProgram.length()>0) {\r
- Retirement retirement = new Retirement(src, i);\r
- resultID = addEntry(entryMap, retirement);\r
- } else {\r
- Program prog = new Program(1, Hex.decode(output.controlProgram));\r
- Output oup = new Output(src, prog, i);\r
- resultID = addEntry(entryMap, oup);\r
- }\r
- resultIDList.add(resultID);\r
- output.id = resultID.toString();\r
-\r
- ValueDestination destination = new ValueDestination(resultID, src.value, 0);\r
- mux.witnessDestinations.add(destination);\r
- }\r
-\r
- TxHeader txHeader = new TxHeader(tx.version, tx.size, tx.timeRange, resultIDList.toArray(new Hash[]{}));\r
- Hash txID = addEntry(entryMap, txHeader);\r
- tx.txID = txID.toString();\r
- } catch (Exception e) {\r
- throw new RuntimeException(e);\r
- }\r
- return tx;\r
- }\r
-\r
- private static Hash addEntry(Map<Hash, Entry> entryMap, Entry entry) {\r
- Hash id = entry.entryID();\r
- entryMap.put(id, entry);\r
- return id;\r
- }\r
-}\r
--- /dev/null
+package io.bytom.api;
+
+import io.bytom.common.Utils;
+import org.bouncycastle.util.encoders.Hex;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Map;
+
+public class Output {
+
+ /**
+ * address
+ */
+ public String address;
+
+ /**
+ * The number of units of the asset being controlled.
+ */
+ public long amount;
+
+ /**
+ * The definition of the asset being controlled (possibly null).
+ */
+ public Map<String, Object> assetDefinition;
+
+ /**
+ * The id of the asset being controlled.
+ */
+ public String assetId;
+
+ /**
+ * The control program which must be satisfied to transfer this output.
+ */
+ public String controlProgram;
+
+ /**
+ * The id of the output.
+ */
+ public String id;
+
+ /**
+ * The output's position in a transaction's list of outputs.
+ */
+ public Integer position;
+
+ public Output(String assetId, long amount, String controlProgram) {
+ this.assetId = assetId;
+ this.amount = amount;
+ this.controlProgram = controlProgram;
+ }
+
+ public byte[] serializeOutput() throws IOException {
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+
+ //assetVersion
+ Utils.writeVarint(1, stream); //AssetVersion是否默认为1
+ //outputCommit
+ ByteArrayOutputStream outputCommitSteam = new ByteArrayOutputStream();
+ //assetId
+ outputCommitSteam.write(Hex.decode(assetId));
+ //amount
+ Utils.writeVarint(amount, outputCommitSteam);
+ //vmVersion
+ Utils.writeVarint(1, outputCommitSteam); //db中获取vm_version
+ //controlProgram
+ Utils.writeVarStr(Hex.decode(controlProgram), outputCommitSteam);
+
+ byte[] dataOutputCommit = outputCommitSteam.toByteArray();
+ //outputCommit的length
+ Utils.writeVarint(dataOutputCommit.length, stream);
+ stream.write(dataOutputCommit);
+
+ //outputWitness
+ Utils.writeVarint(0, stream);
+ return stream.toByteArray();
+ }
+
+ /**
+ * The type the output.<br>
+ * Possible values are "control" and "retire".
+ */
+ public String type;
+
+ public Output setAddress(String address) {
+ this.address = address;
+ return this;
+ }
+
+ public Output setAmount(long amount) {
+ this.amount = amount;
+ return this;
+ }
+
+ public Output setAssetId(String assetId) {
+ this.assetId = assetId;
+ return this;
+ }
+
+ public Output setControlProgram(String controlProgram) {
+ this.controlProgram = controlProgram;
+ return this;
+ }
+
+ public Output setPosition(Integer position) {
+ this.position = position;
+ return this;
+ }
+}
+++ /dev/null
-package io.bytom.api;\r
-\r
-import io.bytom.common.DerivePrivateKey;\r
-import io.bytom.common.DeriveXpub;\r
-import io.bytom.common.ExpandedPrivateKey;\r
-import io.bytom.common.Utils;\r
-import org.bouncycastle.jcajce.provider.digest.SHA3;\r
-import org.bouncycastle.util.encoders.Hex;\r
-\r
-import java.io.ByteArrayOutputStream;\r
-import java.io.IOException;\r
-import java.math.BigInteger;\r
-import java.security.InvalidKeyException;\r
-import java.security.NoSuchAlgorithmException;\r
-import java.security.SignatureException;\r
-\r
-\r
-/**\r
- * Created by liqiang on 2018/10/24.\r
- */\r
-public class SignTransaction {\r
-\r
- private final String OP_TXSIGHASH = "ae";\r
- private final String OP_CHECKMULTISIG = "ad";\r
-\r
- public String serializeTransaction(Transaction tx) {\r
- String txSign = null;\r
- //开始序列化\r
- ByteArrayOutputStream stream = new ByteArrayOutputStream();\r
- try {\r
- stream.write(7);\r
- // version\r
- if (null != tx.version)\r
- Utils.writeVarint(tx.version, stream);\r
- if (null != tx.timeRange)\r
- Utils.writeVarint(tx.timeRange, stream);\r
- //inputs\r
- if (null != tx.inputs && tx.inputs.size() > 0) {\r
- Utils.writeVarint(tx.inputs.size(), stream);\r
- for (Transaction.AnnotatedInput input : tx.inputs) {\r
- if (input.type == 1) {\r
- //assertVersion\r
- Utils.writeVarint(tx.version, stream); //AssetVersion是否默认为1\r
-\r
- //inputCommitment\r
- ByteArrayOutputStream inputCommitStream = new ByteArrayOutputStream();\r
- //spend type flag\r
- Utils.writeVarint(input.type, inputCommitStream);\r
- //spendCommitment\r
- ByteArrayOutputStream spendCommitSteam = new ByteArrayOutputStream();\r
- spendCommitSteam.write(Hex.decode(input.sourceId)); //计算muxID\r
- spendCommitSteam.write(Hex.decode(input.assetId));\r
- Utils.writeVarint(input.amount, spendCommitSteam);\r
- //sourcePosition\r
- Utils.writeVarint(input.sourcePosition, spendCommitSteam); //db中获取position\r
- //vmVersion\r
- Utils.writeVarint(1, spendCommitSteam); //db中获取vm_version\r
- //controlProgram\r
- Utils.writeVarStr(Hex.decode(input.controlProgram), spendCommitSteam);\r
-\r
- byte[] dataSpendCommit = spendCommitSteam.toByteArray();\r
-\r
- Utils.writeVarint(dataSpendCommit.length, inputCommitStream);\r
- inputCommitStream.write(dataSpendCommit);\r
- byte[] dataInputCommit = inputCommitStream.toByteArray();\r
- //inputCommit的length\r
- Utils.writeVarint(dataInputCommit.length, stream);\r
- stream.write(dataInputCommit);\r
-\r
- //inputWitness\r
- if (null != input.witnessComponent) {\r
- ByteArrayOutputStream witnessStream = new ByteArrayOutputStream();\r
- //arguments\r
- int lenSigs = input.witnessComponent.signatures.length;\r
- //arguments的length\r
- Utils.writeVarint(lenSigs, witnessStream);\r
- for (int i = 0; i < lenSigs; i++) {\r
- String sig = input.witnessComponent.signatures[i];\r
- Utils.writeVarStr(Hex.decode(sig), witnessStream);\r
- }\r
- byte[] dataWitnessComponets = witnessStream.toByteArray();\r
- //witness的length\r
- Utils.writeVarint(dataWitnessComponets.length, stream);\r
- stream.write(dataWitnessComponets);\r
- }\r
- }\r
- if (input.type == 0) {\r
- //assetVersion\r
- Utils.writeVarint(01, stream);\r
- //写入 type nonce assetId amount\r
- ByteArrayOutputStream issueInfo = new ByteArrayOutputStream();\r
- //写入 input.type==00 issue\r
- Utils.writeVarint(input.type, issueInfo);\r
- //写入 8个字节的随机数\r
- Utils.writeVarStr(Hex.decode(input.nonce), issueInfo);\r
- issueInfo.write(Hex.decode(input.assetId));\r
- Utils.writeVarint(input.amount, issueInfo);\r
- stream.write(issueInfo.toByteArray().length);\r
- stream.write(issueInfo.toByteArray());\r
-\r
- ByteArrayOutputStream issueInfo1 = new ByteArrayOutputStream();\r
- //未知\r
- Utils.writeVarint(1, issueInfo1);\r
- //写入assetDefine\r
- Utils.writeVarStr(Hex.decode(input.assetDefinition), issueInfo1);\r
- //vm.version\r
- Utils.writeVarint(1, issueInfo1);\r
- //controlProgram\r
- Utils.writeVarStr(Hex.decode(input.controlProgram), issueInfo1);\r
-\r
- //inputWitness\r
- if (null != input.witnessComponent) {\r
- ByteArrayOutputStream witnessStream = new ByteArrayOutputStream();\r
- //arguments\r
- int lenSigs = input.witnessComponent.signatures.length;\r
- //arguments的length\r
- Utils.writeVarint(lenSigs, witnessStream);\r
- for (int i = 0; i < lenSigs; i++) {\r
- String sig = input.witnessComponent.signatures[i];\r
- Utils.writeVarStr(Hex.decode(sig), witnessStream);\r
- }\r
-// byte[] dataWitnessComponets = witnessStream.toByteArray();\r
- //witness的length\r
-// Utils.writeVarint(dataWitnessComponets.length, issueInfo1);\r
-\r
- issueInfo1.write(witnessStream.toByteArray());\r
- }\r
- stream.write(issueInfo1.toByteArray().length - 1);\r
- stream.write(issueInfo1.toByteArray());\r
-\r
- }\r
- }\r
- }\r
-\r
- //outputs\r
- if (null != tx.outputs && tx.outputs.size() > 0) {\r
- Utils.writeVarint(tx.outputs.size(), stream);\r
- for (Transaction.AnnotatedOutput output : tx.outputs) {\r
- //assertVersion\r
- Utils.writeVarint(tx.version, stream); //AssetVersion是否默认为1\r
- //outputCommit\r
- ByteArrayOutputStream outputCommitSteam = new ByteArrayOutputStream();\r
- //assetId\r
- outputCommitSteam.write(Hex.decode(output.assetId));\r
- //amount\r
- Utils.writeVarint(output.amount, outputCommitSteam);\r
- //vmVersion\r
- Utils.writeVarint(1, outputCommitSteam); //db中获取vm_version\r
- //controlProgram\r
- Utils.writeVarStr(Hex.decode(output.controlProgram), outputCommitSteam);\r
-\r
- byte[] dataOutputCommit = outputCommitSteam.toByteArray();\r
- //outputCommit的length\r
- Utils.writeVarint(dataOutputCommit.length, stream);\r
- stream.write(dataOutputCommit);\r
-\r
- //outputWitness\r
- Utils.writeVarint(0, stream);\r
- }\r
- }\r
-\r
- byte[] data = stream.toByteArray();\r
- txSign = Hex.toHexString(data);\r
-\r
- } catch (IOException e) {\r
- throw new RuntimeException(e);\r
- }\r
- return txSign;\r
- }\r
-\r
- public Integer getTransactionSize(Transaction tx) {\r
- String result = serializeTransaction(tx);\r
- return Hex.decode(result).length;\r
- }\r
-\r
- //签名组装witness\r
- public Transaction buildWitness(Transaction transaction, int index, byte[] privateKeyBytes) {\r
-\r
- Transaction.AnnotatedInput input = transaction.inputs.get(index);\r
- if (null == input.witnessComponent)\r
-\r
- if (null != input) {\r
- try {\r
- input.witnessComponent = new Transaction.InputWitnessComponent();\r
- byte[] message = hashFn(Hex.decode(input.inputID), Hex.decode(transaction.txID));\r
- byte[] expandedPrivateKey = ExpandedPrivateKey.ExpandedPrivateKey(privateKeyBytes);\r
- byte[] sig = Signer.Ed25519InnerSign(expandedPrivateKey, message);\r
-\r
- switch (input.type) {\r
- case 1: {\r
- input.witnessComponent.signatures = new String[2];\r
- input.witnessComponent.signatures[0] = Hex.toHexString(sig);\r
- byte[] deriveXpub = DeriveXpub.deriveXpub(privateKeyBytes);\r
- String pubKey = Hex.toHexString(deriveXpub).substring(0, 64);\r
- input.witnessComponent.signatures[1] = pubKey;\r
- break;\r
- }\r
- case 0: {\r
- input.witnessComponent.signatures = new String[1];\r
- input.witnessComponent.signatures[0] = Hex.toHexString(sig);\r
- break;\r
- }\r
- }\r
-\r
- } catch (Exception e) {\r
- throw new RuntimeException(e);\r
- }\r
- } else {\r
- System.out.println("build witness failed.");\r
- }\r
- return transaction;\r
- }\r
-\r
- //多签单输入\r
- public void buildWitness(Transaction transaction, int index, String[] privateKeys) {\r
- Transaction.AnnotatedInput input = transaction.inputs.get(index);\r
- if (null == input.witnessComponent)\r
- input.witnessComponent = new Transaction.InputWitnessComponent();\r
- try {\r
-\r
- //TODO 多签issue\r
- StringBuilder sb = new StringBuilder(OP_TXSIGHASH);\r
- input.witnessComponent.signatures = new String[privateKeys.length + 1];\r
- for (int i = 0; i < privateKeys.length; i++) {\r
- System.out.println(input.isChange());\r
- byte[] key = DerivePrivateKey.derivePrivateKey(privateKeys[i], 1, input.isChange(), input.getControlProgramIndex());\r
- byte[] message = hashFn(Hex.decode(input.inputID), Hex.decode(transaction.txID));\r
- byte[] expandedPrivateKey = ExpandedPrivateKey.ExpandedPrivateKey(key);\r
- byte[] sig = Signer.Ed25519InnerSign(expandedPrivateKey, message);\r
- input.witnessComponent.signatures[i] = Hex.toHexString(sig);\r
- byte[] deriveXpub = DeriveXpub.deriveXpub(expandedPrivateKey);\r
- String publicKey = Hex.toHexString(deriveXpub).substring(0, 64);\r
- sb.append(Integer.toString(Hex.decode(publicKey).length,16)).append(publicKey);\r
- }\r
- //TODO 签名数跟公钥数量不同\r
- sb.append(Utils.pushDataInt(privateKeys.length)); //should be nrequired\r
- sb.append(Utils.pushDataInt(privateKeys.length));\r
- sb.append(OP_CHECKMULTISIG);\r
- input.witnessComponent.signatures[privateKeys.length] = sb.toString();\r
-\r
- } catch (Exception e) {\r
- throw new RuntimeException(e);\r
- }\r
-\r
- }\r
-\r
- public static byte[] hashFn(byte[] hashedInputHex, byte[] txID) {\r
-\r
- SHA3.Digest256 digest256 = new SHA3.Digest256();\r
- // data = hashedInputHex + txID\r
- ByteArrayOutputStream out = new ByteArrayOutputStream();\r
- out.write(hashedInputHex, 0, hashedInputHex.length);\r
- out.write(txID, 0, txID.length);\r
- byte[] data = out.toByteArray();\r
- return digest256.digest(data);\r
- }\r
-\r
-\r
- public Transaction generateSignatures(Transaction Transaction, BigInteger[] keys) {\r
- Transaction.AnnotatedInput input = Transaction.inputs.get(0);\r
- input.witnessComponent.signatures = new String[keys.length];\r
- for (int i = 0; i < keys.length; i++) {\r
- if (null != input) {\r
- try {\r
- byte[] message = hashFn(Hex.decode(input.inputID), Hex.decode(Transaction.txID));\r
- byte[] expandedPrv = Utils.BigIntegerToBytes(keys[i]);\r
- byte[] priKey = ExpandedPrivateKey.ExpandedPrivateKey(expandedPrv);\r
- byte[] sig = Signer.Ed25519InnerSign(priKey, message);\r
- input.witnessComponent.signatures[i] = Hex.toHexString(sig);\r
-\r
- } catch (Exception e) {\r
- e.printStackTrace();\r
- }\r
- } else {\r
- System.out.println("generate signatures failed.");\r
- }\r
- }\r
- return Transaction;\r
- }\r
-\r
- //根据主私钥对交易签名,生成序列化的rawTransaction\r
- public String rawTransaction(String rootPrivateKey, Transaction tx) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {\r
- byte[] privateChild;\r
- for (int i = 0; i < tx.inputs.size(); i++) {\r
- if (tx.inputs.get(i).type == 1) {\r
- privateChild = DerivePrivateKey.derivePrivateKey(rootPrivateKey, 1, tx.inputs.get(i).isChange(), tx.inputs.get(i).getControlProgramIndex());\r
- } else {\r
- privateChild = DerivePrivateKey.derivePrivateKey(rootPrivateKey, tx.inputs.get(i).getKeyIndex());\r
- }\r
- buildWitness(tx, i, privateChild);\r
- }\r
- return serializeTransaction(tx);\r
- }\r
-\r
- //多签\r
- public String rawTransaction(String[] rootPrivateKey, Transaction tx) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {\r
- String privateChild;\r
- for (int i = 0; i < tx.inputs.size(); i++) {\r
- if (tx.inputs.get(i).controlProgram.length() != 44) {\r
- buildWitness(tx, i, rootPrivateKey);\r
- }\r
-\r
- }\r
- return serializeTransaction(tx);\r
- }\r
-\r
-\r
-}\r
-\r
-\r
\r
public class Signer {\r
\r
- public static byte[] Ed25519InnerSign(byte[] privateKey, byte[] message)\r
- throws SignatureException, NoSuchAlgorithmException, InvalidKeyException {\r
+ public static byte[] ed25519InnerSign(byte[] privateKey, byte[] message) throws NoSuchAlgorithmException {\r
MessageDigest md = MessageDigest.getInstance("SHA-512");\r
byte[] digestData = new byte[32 + message.length];\r
int digestDataIndex = 0;\r
--- /dev/null
+package io.bytom.api;
+
+import io.bytom.common.*;
+import io.bytom.types.*;
+import org.bouncycastle.util.encoders.Hex;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Map;
+
+public class SpendInput extends BaseInput {
+
+ private String sourceId;
+
+ private long sourcePosition;
+
+ private boolean change;
+
+ private int controlProgramIndex;
+
+ private BIPProtocol bipProtocol;
+
+ public SpendInput() {}
+
+ public SpendInput(String assetID, long amount, String controlProgram) {
+ this.setAssetId(assetID);
+ this.setAmount(amount);
+ this.setProgram(controlProgram);
+ this.bipProtocol = BIPProtocol.BIP44;
+ }
+
+ @Override
+ public InputEntry convertInputEntry(Map<Hash, Entry> entryMap, int index) {
+ Program pro = new Program(this.getVmVersion(), Hex.decode(this.getProgram()));
+ AssetAmount assetAmount = this.getAssetAmount();
+ Hash sourceID = new Hash(this.sourceId);
+ ValueSource src = new ValueSource(sourceID, assetAmount, this.sourcePosition);
+
+ OutputEntry prevout = new OutputEntry(src, pro, 0);
+ Hash prevOutID = prevout.entryID();
+ entryMap.put(prevOutID, prevout);
+ return new Spend(prevOutID, index);
+ }
+
+ @Override
+ public byte[] serializeInput() throws IOException {
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ //assetVersion
+ Utils.writeVarint(1, stream); //AssetVersion是否默认为1
+
+ //inputCommitment
+ ByteArrayOutputStream inputCommitStream = new ByteArrayOutputStream();
+ //spend type flag
+ Utils.writeVarint(SPEND_INPUT_TYPE, inputCommitStream);
+ //spendCommitment
+ ByteArrayOutputStream spendCommitSteam = new ByteArrayOutputStream();
+ spendCommitSteam.write(Hex.decode(sourceId)); //计算muxID
+ spendCommitSteam.write(Hex.decode(getAssetId()));
+ Utils.writeVarint(getAmount(), spendCommitSteam);
+ //sourcePosition
+ Utils.writeVarint(sourcePosition, spendCommitSteam); //db中获取position
+ //vmVersion
+ Utils.writeVarint(1, spendCommitSteam); //db中获取vm_version
+ //controlProgram
+ Utils.writeVarStr(Hex.decode(getProgram()), spendCommitSteam);
+
+ byte[] dataSpendCommit = spendCommitSteam.toByteArray();
+
+ Utils.writeVarint(dataSpendCommit.length, inputCommitStream);
+ inputCommitStream.write(dataSpendCommit);
+ byte[] dataInputCommit = inputCommitStream.toByteArray();
+ //inputCommit的length
+ Utils.writeVarint(dataInputCommit.length, stream);
+ stream.write(dataInputCommit);
+
+ //inputWitness
+ if (null != getWitnessComponent()) {
+ ByteArrayOutputStream witnessStream = new ByteArrayOutputStream();
+ //arguments
+ int witnessSize = getWitnessComponent().size();
+ //arguments的length
+ Utils.writeVarint(witnessSize, witnessStream);
+ for (int i = 0; i < witnessSize; i++) {
+ String witness = getWitnessComponent().getWitness(i);
+ Utils.writeVarStr(Hex.decode(witness), witnessStream);
+ }
+ byte[] dataWitnessComponents = witnessStream.toByteArray();
+ //witness的length
+ Utils.writeVarint(dataWitnessComponents.length, stream);
+ stream.write(dataWitnessComponents);
+ }
+ return stream.toByteArray();
+ }
+
+ @Override
+ public void buildWitness(String txID) throws Exception {
+ String rootPrvKey = getWitnessComponent().getRootPrivateKey();
+
+ byte[] privateChild;
+ if (bipProtocol == BIPProtocol.BIP44) {
+ privateChild = DerivePrivateKey.bip44derivePrvKey(rootPrvKey, TransactionSigner.AccountKeySpace, change, controlProgramIndex);
+ } else {
+ privateChild = DerivePrivateKey.bip32derivePrvKey(rootPrvKey, getKeyIndex(), TransactionSigner.AccountKeySpace, controlProgramIndex);
+ }
+
+ byte[] message = Utils.hashFn(Hex.decode(getInputID()), Hex.decode(txID));
+ byte[] expandedPrivateKey = ExpandedPrivateKey.expandedPrivateKey(privateChild);
+ byte[] sig = Signer.ed25519InnerSign(expandedPrivateKey, message);
+
+ getWitnessComponent().appendWitness(Hex.toHexString(sig));
+
+ byte[] deriveXpub = DeriveXpub.deriveXpub(privateChild);
+ String pubKey = Hex.toHexString(deriveXpub).substring(0, 64);
+
+ getWitnessComponent().appendWitness(pubKey);
+ }
+
+ public void setSourceId(String sourceId) {
+ this.sourceId = sourceId;
+ }
+
+ public void setSourcePosition(long sourcePosition) {
+ this.sourcePosition = sourcePosition;
+ }
+
+ public void setChange(boolean change) {
+ this.change = change;
+ }
+
+ public void setControlProgramIndex(int controlProgramIndex) {
+ this.controlProgramIndex = controlProgramIndex;
+ }
+
+ public void setBipProtocol(BIPProtocol bipProtocol) {
+ this.bipProtocol = bipProtocol;
+ }
+}
\ No newline at end of file
package io.bytom.api;\r
\r
-import com.google.gson.annotations.SerializedName;\r
-import io.bytom.common.ParameterizedTypeImpl;\r
-import io.bytom.common.SuccessRespon;\r
import io.bytom.common.Utils;\r
-import io.bytom.exception.BytomException;\r
-import io.bytom.http.Client;\r
-import org.apache.log4j.Logger;\r
+import io.bytom.types.*;\r
+import org.bouncycastle.util.encoders.Hex;\r
\r
-import java.lang.reflect.Type;\r
+import java.io.ByteArrayOutputStream;\r
+import java.io.IOException;\r
import java.util.ArrayList;\r
import java.util.HashMap;\r
import java.util.List;\r
\r
public class Transaction {\r
\r
- @SerializedName("tx_id")\r
public String txID;\r
/**\r
* version\r
/**\r
* time_range\r
*/\r
- @SerializedName("time_range")\r
public Integer timeRange;\r
\r
/**\r
- * status\r
- */\r
- public Integer fee;\r
-\r
- /**\r
* List of specified inputs for a transaction.\r
*/\r
- public List<AnnotatedInput> inputs;\r
+ public List<BaseInput> inputs;\r
\r
/**\r
* List of specified outputs for a transaction.\r
*/\r
- public List<AnnotatedOutput> outputs;\r
-\r
- // public InputWitnessComponent inputWitnessComponent;\r
- private static Logger logger = Logger.getLogger(Transaction.class);\r
-\r
- public String toJson() {\r
- return Utils.serializer.toJson(this);\r
+ public List<Output> outputs;\r
+\r
+ public Transaction(Builder builder) {\r
+ this.inputs = builder.inputs;\r
+ this.outputs = builder.outputs;\r
+ this.version = builder.version;\r
+ this.size = builder.size;\r
+ this.timeRange = builder.timeRange;\r
+ mapTx();\r
+ sign();\r
}\r
\r
- public static Transaction fromJson(String json) {\r
- return Utils.serializer.fromJson(json, Transaction.class);\r
- }\r
-\r
- public static Transaction fromSuccessRespon(String json) {\r
- Type responType = new ParameterizedTypeImpl(SuccessRespon.class, new Class[]{Transaction.class});\r
- SuccessRespon<Transaction> result = Utils.serializer.fromJson(json, responType);\r
- return result.dataObject;\r
- }\r
+ public static class Builder {\r
\r
- public static Transaction decode(Client client, String txId) throws BytomException {\r
- Map<String, Object> req = new HashMap<String, Object>();\r
- req.put("raw_transaction", txId);\r
- Transaction Transaction =\r
- client.request("decode-raw-transaction", req, Transaction.class);\r
+ private String txID;\r
\r
- logger.info("decode-raw-transaction:");\r
- logger.info(Transaction.toJson());\r
+ private Integer version = 1;\r
\r
- return Transaction;\r
- }\r
+ private Integer size = 0;\r
\r
- public static class Builder {\r
- @SerializedName("tx_id")\r
- public String txID;\r
- public Integer version;\r
- public Integer size;\r
- @SerializedName("time_range")\r
- public Integer timeRange;\r
+ private Integer timeRange;\r
\r
- Transaction tx;\r
- List<AnnotatedInput> inputs;\r
- List<AnnotatedOutput> outputs;\r
+ private List<BaseInput> inputs;\r
+ private List<Output> outputs;\r
\r
public Builder() {\r
this.inputs = new ArrayList<>();\r
this.outputs = new ArrayList<>();\r
}\r
\r
- public Builder addInput(AnnotatedInput input) {\r
+ public Builder addInput(BaseInput input) {\r
this.inputs.add(input);\r
return this;\r
}\r
\r
- public Builder addOutput(AnnotatedOutput output) {\r
+ public Builder addOutput(Output output) {\r
this.outputs.add(output);\r
return this;\r
}\r
\r
-\r
- public Transaction build(Integer version, Integer timeRange, Integer size) {\r
- tx = new Transaction();\r
- tx.inputs = this.inputs;\r
- tx.outputs = this.outputs;\r
- tx.version = version;\r
- tx.timeRange = timeRange;\r
- tx.size = size;\r
- return tx;\r
- }\r
-\r
- public Transaction build(int timeRange) {\r
- tx = new Transaction();\r
- tx.inputs = this.inputs;\r
- tx.outputs = this.outputs;\r
- tx.version = 1;\r
- tx.size = 0;\r
- tx.timeRange = timeRange;\r
- return tx;\r
- }\r
- }\r
-\r
- public static class AnnotatedInput {\r
-\r
- @SerializedName("input_id")\r
- public String inputID;\r
- /**\r
- * address\r
- */\r
- public String address;\r
-\r
- /**\r
- * The number of units of the asset being issued or spent.\r
- */\r
- public long amount;\r
-\r
- // /**\r
-// * The definition of the asset being issued or spent (possibly null).\r
-// */\r
-// @SerializedName("asset_definition")\r
-// private Map<String, Object> assetDefinition;\r
- @SerializedName("asset_definition")\r
- public String assetDefinition;\r
-\r
- /**\r
- * The id of the asset being issued or spent.\r
- */\r
- @SerializedName("asset_id")\r
- public String assetId;\r
-\r
- /**\r
- * The control program which must be satisfied to transfer this output.\r
- */\r
- @SerializedName("control_program")\r
- public String controlProgram;\r
-\r
- /**\r
- * The id of the output consumed by this input. Null if the input is an\r
- * issuance.\r
- */\r
- @SerializedName("spent_output_id")\r
- public String spentOutputId;\r
-\r
- /**\r
- * The type of the input.<br>\r
- * Possible values are "issue" and "spend".\r
- */\r
- public int type;\r
-\r
- public String sourceId;\r
-\r
- public long sourcePosition;\r
-\r
- public String nonce;\r
-\r
- private int controlProgramIndex;\r
- private boolean change;\r
-\r
- public int keyIndex;\r
- public String chainPath;\r
-\r
- @SerializedName("witness_component")\r
- public InputWitnessComponent witnessComponent;\r
-\r
- @Override\r
- public String toString() {\r
- return Utils.serializer.toJson(this);\r
- }\r
-\r
- public int getControlProgramIndex() {\r
- return controlProgramIndex;\r
- }\r
-\r
- public AnnotatedInput setControlProgramIndex(int controlProgramIndex) {\r
- this.controlProgramIndex = controlProgramIndex;\r
+ public Builder setTimeRange(int timeRange) {\r
+ this.timeRange = timeRange;\r
return this;\r
}\r
\r
- public boolean isChange() {\r
- return change;\r
- }\r
-\r
- public AnnotatedInput setChange(boolean change) {\r
- this.change = change;\r
- return this;\r
- }\r
-\r
- public AnnotatedInput setAmount(long amount) {\r
- this.amount = amount;\r
- return this;\r
+ public Transaction build() {\r
+ return new Transaction(this);\r
}\r
-\r
- public AnnotatedInput setAssetId(String assetId) {\r
- this.assetId = assetId;\r
- return this;\r
- }\r
-\r
- public AnnotatedInput setControlProgram(String controlProgram) {\r
- this.controlProgram = controlProgram;\r
- return this;\r
- }\r
-\r
- public AnnotatedInput setType(int type) {\r
- this.type = type;\r
- return this;\r
- }\r
-\r
- public AnnotatedInput setSourceId(String sourceId) {\r
- this.sourceId = sourceId;\r
- return this;\r
- }\r
-\r
- public AnnotatedInput setSourcePosition(long sourcePosition) {\r
- this.sourcePosition = sourcePosition;\r
- return this;\r
- }\r
-\r
- public AnnotatedInput setNonce(String nonce) {\r
- this.nonce = nonce;\r
- return this;\r
- }\r
-\r
- public AnnotatedInput setAssetDefinition(String assetDefinition) {\r
- this.assetDefinition = assetDefinition;\r
- return this;\r
- }\r
-\r
- public int getKeyIndex() {\r
- return keyIndex;\r
- }\r
-\r
- public AnnotatedInput setKeyIndex(int keyIndex) {\r
- this.keyIndex = keyIndex;\r
- return this;\r
- }\r
-\r
}\r
\r
- public static class AnnotatedOutput {\r
-\r
- /**\r
- * address\r
- */\r
- public String address;\r
-\r
- /**\r
- * The number of units of the asset being controlled.\r
- */\r
- public long amount;\r
-\r
- /**\r
- * The definition of the asset being controlled (possibly null).\r
- */\r
- @SerializedName("asset_definition")\r
- public Map<String, Object> assetDefinition;\r
-\r
- /**\r
- * The id of the asset being controlled.\r
- */\r
- @SerializedName("asset_id")\r
- public String assetId;\r
-\r
- /**\r
- * The control program which must be satisfied to transfer this output.\r
- */\r
- @SerializedName("control_program")\r
- public String controlProgram;\r
-\r
- /**\r
- * The id of the output.\r
- */\r
- @SerializedName("id")\r
- public String id;\r
-\r
- /**\r
- * The output's position in a transaction's list of outputs.\r
- */\r
- public Integer position;\r
-\r
- /**\r
- * The type the output.<br>\r
- * Possible values are "control" and "retire".\r
- */\r
- public String type;\r
-\r
- public AnnotatedOutput setAddress(String address) {\r
- this.address = address;\r
- return this;\r
- }\r
-\r
- public AnnotatedOutput setAmount(long amount) {\r
- this.amount = amount;\r
- return this;\r
- }\r
-\r
- public AnnotatedOutput setAssetId(String assetId) {\r
- this.assetId = assetId;\r
- return this;\r
+ private void sign() {\r
+ for (BaseInput input : inputs) {\r
+ try {\r
+ input.buildWitness(txID);\r
+ } catch (Exception e) {\r
+ e.printStackTrace();\r
+ throw new RuntimeException(e);\r
+ }\r
}\r
+ }\r
\r
- public AnnotatedOutput setControlProgram(String controlProgram) {\r
- this.controlProgram = controlProgram;\r
- return this;\r
- }\r
+ public String rawTransaction() {\r
+ String rawTransaction;\r
+ //开始序列化\r
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();\r
+ try {\r
+ stream.write(7);\r
+ // version\r
+ if (null != version)\r
+ Utils.writeVarint(version, stream);\r
+ if (null != timeRange)\r
+ Utils.writeVarint(timeRange, stream);\r
+ //inputs\r
+ if (null != inputs && inputs.size() > 0) {\r
+ Utils.writeVarint(inputs.size(), stream);\r
+ for (BaseInput input : inputs) {\r
+ System.out.println(Hex.toHexString(input.serializeInput()));\r
+ stream.write(input.serializeInput());\r
+ }\r
+ }\r
+\r
+ //outputs\r
+ if (null != outputs && outputs.size() > 0) {\r
+ Utils.writeVarint(outputs.size(), stream);\r
+ for (Output output : outputs) {\r
+ stream.write(output.serializeOutput());\r
+ }\r
+ }\r
+ byte[] data = stream.toByteArray();\r
+ rawTransaction = Hex.toHexString(data);\r
+ } catch (IOException e) {\r
+ throw new RuntimeException(e);\r
+ }\r
+ return rawTransaction;\r
+ }\r
\r
- public AnnotatedOutput setPosition(Integer position) {\r
- this.position = position;\r
- return this;\r
+ private void mapTx() {\r
+ Map<Hash, Entry> entryMap = new HashMap<>();\r
+ ValueSource[] muxSources = new ValueSource[inputs.size()];\r
+ List<InputEntry> inputEntries = new ArrayList<>();\r
+\r
+ try {\r
+ for (int i = 0; i < inputs.size(); i++) {\r
+ BaseInput input = inputs.get(i);\r
+ InputEntry inputEntry = input.convertInputEntry(entryMap, i);\r
+ Hash spendID = addEntry(entryMap, inputEntry);\r
+ input.setInputID(spendID.toString());\r
+\r
+ muxSources[i] = new ValueSource(spendID, input.getAssetAmount(), 0);\r
+ inputEntries.add(inputEntry);\r
+ }\r
+\r
+ Mux mux = new Mux(muxSources, new Program(1, new byte[]{0x51}));\r
+ Hash muxID = addEntry(entryMap, mux);\r
+ for (InputEntry inputEntry : inputEntries) {\r
+ inputEntry.setDestination(muxID, inputEntry.ordinal, entryMap);\r
+ }\r
+\r
+ List<Hash> resultIDList = new ArrayList<>();\r
+ for (int i = 0; i < outputs.size(); i++) {\r
+ Output output = outputs.get(i);\r
+\r
+ AssetAmount amount = new AssetAmount(new AssetID(output.assetId), output.amount);\r
+ ValueSource src = new ValueSource(muxID, amount, i);\r
+\r
+ Hash resultID;\r
+ if (output.controlProgram.startsWith("6a")) {\r
+ Retirement retirement = new Retirement(src, i);\r
+ resultID = addEntry(entryMap, retirement);\r
+ } else {\r
+ Program prog = new Program(1, Hex.decode(output.controlProgram));\r
+ OutputEntry oup = new OutputEntry(src, prog, i);\r
+ resultID = addEntry(entryMap, oup);\r
+ }\r
+\r
+ resultIDList.add(resultID);\r
+ output.id = resultID.toString();\r
+\r
+ ValueDestination destination = new ValueDestination(resultID, src.value, 0);\r
+ mux.witnessDestinations.add(destination);\r
+ }\r
+\r
+ TxHeader txHeader = new TxHeader(version, size, timeRange, resultIDList.toArray(new Hash[]{}));\r
+ Hash txID = addEntry(entryMap, txHeader);\r
+ this.txID = txID.toString();\r
+\r
+ } catch (Exception e) {\r
+ throw new RuntimeException(e);\r
}\r
}\r
\r
- /**\r
- * A single witness component, holding information that will become the input\r
- * witness.\r
- */\r
- public static class InputWitnessComponent {\r
-\r
- /**\r
- * The list of signatures made with the specified keys (null unless type is\r
- * "signature").\r
- */\r
- public String[] signatures;\r
+ private Hash addEntry(Map<Hash, Entry> entryMap, Entry entry) {\r
+ Hash id = entry.entryID();\r
+ entryMap.put(id, entry);\r
+ return id;\r
}\r
}\r
--- /dev/null
+package io.bytom.api;\r
+\r
+/**\r
+ * Created by liqiang on 2018/10/24.\r
+ */\r
+public class TransactionSigner {\r
+\r
+ public static final byte AssetKeySpace = 0;\r
+ public static final byte AccountKeySpace = 1;\r
+}\r
+\r
+\r
return Utils.serializer.fromJson(json, UTXO.class);\r
}\r
\r
- public static Transaction.AnnotatedInput utxoToAnnotatedInput(UTXO utxo) {\r
- Transaction.AnnotatedInput annotatedInput = new Transaction.AnnotatedInput();\r
- annotatedInput.setAmount(utxo.amount);\r
- annotatedInput.setControlProgram(utxo.program);\r
- annotatedInput.setChange(utxo.change);\r
- annotatedInput.setAssetId(utxo.assetId);\r
- annotatedInput.setControlProgramIndex(utxo.controlProgramIndex);\r
- annotatedInput.setSourceId(utxo.sourceId);\r
- annotatedInput.setSourcePosition(utxo.sourcePos);\r
- return annotatedInput;\r
+ public SpendInput toSpendAnnotatedInput() {\r
+ SpendInput spendInput = new SpendInput();\r
+ spendInput.setAmount(amount);\r
+ spendInput.setProgram(program);\r
+ spendInput.setChange(change);\r
+ spendInput.setAssetId(assetId);\r
+ spendInput.setControlProgramIndex(controlProgramIndex);\r
+ spendInput.setSourceId(sourceId);\r
+ spendInput.setSourcePosition(sourcePos);\r
+ return spendInput;\r
}\r
}\r
--- /dev/null
+package io.bytom.api;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A single witness component, holding information that will become the input
+ * witness.
+ */
+public class WitnessComponent {
+
+ /**
+ * The list of witnesses made with the specified keys (null unless type is
+ * "signature").
+ */
+ private List<String> witnesses;
+
+ private String rootPrivateKey;
+
+ public WitnessComponent() {
+ witnesses = new ArrayList<>();
+ }
+
+ public int size() {
+ return witnesses.size();
+ }
+
+ public String getWitness(int index) {
+ return witnesses.get(index);
+ }
+
+ public void appendWitness(String witness) {
+ witnesses.add(witness);
+ }
+
+ public String getRootPrivateKey() {
+ return rootPrivateKey;
+ }
+
+ public void setRootPrivateKey(String rootPrivateKey) {
+ this.rootPrivateKey = rootPrivateKey;
+ }
+}
\ No newline at end of file
+++ /dev/null
-package io.bytom.common;\r
-\r
-/**\r
- * Created by liqiang on 2018/10/17.\r
- */\r
-public class Constants {\r
-\r
- public static int INPUT_TYPE_ISSUANCE = 0;\r
-\r
- public static int INPUT_TYPE_SPEND = 1;\r
-\r
- public static int INPUT_TYPE_COINBASE = 2;\r
-}\r
public class DerivePrivateKey {\r
//bip44 派生子秘钥\r
//accountIndex 默认为1\r
- public static byte[] derivePrivateKey(String rootPriv, int accountIndex, boolean change, int programIndex) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {\r
- byte[] xprv = Hex.decode(rootPriv);\r
+ public static byte[] bip44derivePrvKey(String rootPriv, int accountIndex, boolean change, int programIndex) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {\r
byte[][] paths = PathUtil.getBip44Path(accountIndex, change, programIndex);\r
- byte[] res = xprv;\r
+ byte[] res = Hex.decode(rootPriv);\r
for (int i = 0; i < paths.length; i++) {\r
byte[] xpub = DeriveXpub.deriveXpub(res);\r
- res = NonHardenedChild.NHchild(paths[i], res, xpub);\r
+ res = NonHardenedChild.nhChild(paths[i], res, xpub);\r
}\r
return res;\r
}\r
\r
//BIP32 派生子秘钥 issue 需要用到BIP32派生规则\r
- public static byte[] derivePrivateKey(String rootPrivateKey, int keyIndex) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {\r
- byte[] xprv = Hex.decode(rootPrivateKey);\r
- byte[] res = xprv;\r
- byte[][] paths = PathUtil.getBip32Path(keyIndex);\r
+ public static byte[] bip32derivePrvKey(String rootPrivateKey, int accountIndex, byte keySpace, long ...programIndex) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {\r
+ byte[] res = Hex.decode(rootPrivateKey);\r
+ byte[][] paths = PathUtil.getBip32Path(keySpace, accountIndex, programIndex);\r
for (int i = 0; i < paths.length; i++) {\r
byte[] xpub = DeriveXpub.deriveXpub(res);\r
- res = NonHardenedChild.NHchild(paths[i], res, xpub);\r
+ res = NonHardenedChild.nhChild(paths[i], res, xpub);\r
}\r
return res;\r
}\r
public static byte[] deriveXpub(byte[] xprv) {\r
byte[] xpub = new byte[xprv.length];\r
byte[] scalar = new byte[xprv.length / 2];\r
-// for (int i = 0; i < xprv.length / 2; i++) {\r
-// scalar[i] = xprv[i];\r
-// }\r
+\r
System.arraycopy(xprv, 0, scalar, 0, xprv.length / 2);\r
byte[] buf = Ed25519.scalarMultWithBaseToBytes(scalar);\r
-// for (int i = 0; i < buf.length; i++) {\r
-// xpub[i] = buf[i];\r
-// }\r
+\r
System.arraycopy(buf, 0, xpub, 0, buf.length);\r
-// for (int i = xprv.length / 2; i < xprv.length; i++) {\r
-// xpub[i] = xprv[i];\r
-// }\r
System.arraycopy(xprv, xprv.length / 2, xpub, xprv.length / 2, xprv.length / 2);\r
return xpub;\r
}\r
import java.security.SignatureException;\r
\r
public class ExpandedPrivateKey {\r
- public static byte[] HMacSha512(byte[] data, byte[] key)\r
+ public static byte[] hmacSha512(byte[] data, byte[] key)\r
throws SignatureException, NoSuchAlgorithmException, InvalidKeyException {\r
SecretKeySpec signingKey = new SecretKeySpec(key, "HmacSHA512");\r
Mac mac = Mac.getInstance("HmacSHA512");\r
return mac.doFinal(data);\r
}\r
\r
- public static byte[] ExpandedPrivateKey(byte[] data)\r
+ public static byte[] expandedPrivateKey(byte[] data)\r
throws SignatureException, NoSuchAlgorithmException, InvalidKeyException {\r
// "457870616e64" is "Expand" hex.\r
- byte[] res = HMacSha512(data, Hex.decode("457870616e64"));\r
+ byte[] res = hmacSha512(data, Hex.decode("457870616e64"));\r
for (int i = 0; i <= 31; i++) {\r
res[i] = data[i];\r
}\r
\r
public class NonHardenedChild {\r
\r
- private static byte[] HMacSha512(byte[] data, byte[] key)\r
+ private static byte[] hmacSha512(byte[] data, byte[] key)\r
throws NoSuchAlgorithmException, InvalidKeyException {\r
SecretKeySpec signingKey = new SecretKeySpec(key, "HmacSHA512");\r
Mac mac = Mac.getInstance("HmacSHA512");\r
return mac.doFinal(data);\r
}\r
\r
- public static byte[] NHchild(byte[] path, byte[] xprv, byte[] xpub) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {\r
+ public static byte[] nhChild(byte[] path, byte[] xprv, byte[] xpub) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {\r
// begin build data\r
ByteArrayOutputStream out = new ByteArrayOutputStream();\r
out.write('N');\r
// end build key\r
\r
// doFinal()\r
- byte[] res = HMacSha512(data, key);\r
+ byte[] res = hmacSha512(data, key);\r
\r
//begin operate res[:32]\r
byte[] f = new byte[res.length / 2];\r
byte[] res = xprv;\r
for (int i = 0; i < hpaths.length; i++) {\r
byte[] xpub = DeriveXpub.deriveXpub(res);\r
- res = NonHardenedChild.NHchild(paths[i], res, xpub);\r
+ res = NonHardenedChild.nhChild(paths[i], res, xpub);\r
}\r
return res;\r
}\r
import com.google.gson.Gson;\r
import com.google.gson.GsonBuilder;\r
import io.bytom.types.ExpandedKeys;\r
+import org.bouncycastle.jcajce.provider.digest.SHA3;\r
import org.bouncycastle.util.encoders.Hex;\r
\r
import java.io.ByteArrayOutputStream;\r
}\r
\r
public static ExpandedKeys expandedPriKey(String priKey, String key) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {\r
- byte[] hashPriKey = ExpandedPrivateKey.HMacSha512(Hex.decode(priKey), key.getBytes());\r
+ byte[] hashPriKey = ExpandedPrivateKey.hmacSha512(Hex.decode(priKey), key.getBytes());\r
//begin operate res[:32]\r
byte[] f = new byte[hashPriKey.length / 2];\r
System.arraycopy(hashPriKey, 0, f, 0, hashPriKey.length / 2);\r
return null;\r
\r
}\r
+\r
+ public static byte[] hashFn(byte[] hashedInputHex, byte[] txID) {\r
+\r
+ SHA3.Digest256 digest256 = new SHA3.Digest256();\r
+ // data = hashedInputHex + txID\r
+ ByteArrayOutputStream out = new ByteArrayOutputStream();\r
+ out.write(hashedInputHex, 0, hashedInputHex.length);\r
+ out.write(txID, 0, txID.length);\r
+ byte[] data = out.toByteArray();\r
+ return digest256.digest(data);\r
+ }\r
}\r
--- /dev/null
+package io.bytom.common;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+public class VMUtil {
+
+ private static final byte OP_0 = (byte) 0x00;
+ private static final byte OP_1 = (byte) 0x51;
+ private static final byte OP_PUSHDATA1 = (byte) 0x4c;
+ private static final byte OP_PUSHDATA2 = (byte) 0x4d;
+ private static final byte OP_PUSHDATA4 = (byte) 0x43;
+ private static final byte OP_TXSIGHASH = (byte) 0xae;
+ private static final byte OP_CHECKMULTISIG = (byte) 0xad;
+
+ public static byte[] p2spMultiSigProgram(byte[][] pubKeys, int nRequired) {
+ checkMultiSigParams(nRequired, pubKeys.length);
+ ByteArrayOutputStream program = new ByteArrayOutputStream();
+ program.write(OP_TXSIGHASH);
+
+ for (byte[] pubKey : pubKeys) {
+ try {
+ program.write(pushDataBytes(pubKey));
+ program.write(pushDataInt64(nRequired));
+ program.write(pushDataInt64(pubKeys.length));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ program.write(OP_CHECKMULTISIG);
+ return program.toByteArray();
+ }
+
+ private static void checkMultiSigParams(int nRequired, int nPubkeys) {
+ if (nRequired < 0 || nPubkeys < 0 || nRequired > nPubkeys || (nRequired == 0 && nPubkeys > 0)) {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ private static byte[] pushDataBytes(byte[] data) {
+ int len = data.length;
+ if (len == 0) {
+ return new byte[] {OP_0};
+ }
+ if (len <= 75) {
+ byte[] dest = new byte[1 + len];
+ dest[0] = (byte) len;
+ System.arraycopy(data, 0, dest, 1, len);
+ return dest;
+ }
+ if (len < 256) {
+ byte[] dest = new byte[2 + len];
+ dest[0] = OP_PUSHDATA1;
+ dest[1] = (byte) len;
+ System.arraycopy(data, 0, dest, 2, len);
+ return dest;
+ }
+ if (len < 65536) {
+ byte[] dest = new byte[3 + len];
+ dest[0] = OP_PUSHDATA2;
+ dest[1] = (byte) len;
+ dest[2] = (byte) (len >> 8);
+ System.arraycopy(data, 0, dest, 3, len);
+ return dest;
+ }
+ byte[] dest = new byte[5 + len];
+ dest[0] = OP_PUSHDATA4;
+ dest[1] = (byte) len;
+ dest[2] = (byte) (len >> 8);
+ dest[3] = (byte) (len >> 16);
+ dest[4] = (byte) (len >> 24);
+ System.arraycopy(data, 0, dest, 5, len);
+ return dest;
+ }
+
+ private static byte[] pushDataInt64(long n) {
+ if (n == 0) {
+ return new byte[] {OP_0};
+ }
+ if (n >= 1 && n <= 16) {
+ return new byte[] {(byte) (OP_1 + (byte) n - 1)};
+ }
+ return pushDataBytes(int64Bytes(n));
+ }
+
+ private static byte[] int64Bytes(long n) {
+ byte[] bytes = new byte[8];
+ int i = 0;
+ while (n != 0) {
+ bytes[i] = (byte) n;
+ n >>= 8;
+ i++;
+ }
+ byte[] res = new byte[i];
+ System.arraycopy(bytes, 0, res, 0, i);
+ return res;
+ }
+
+}
+
}\r
\r
private String computeIssueProgram(String rootKey, int keyIndex) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {\r
- byte[] derivePrivateKey = DerivePrivateKey.derivePrivateKey(rootKey, keyIndex);\r
+ byte[] derivePrivateKey = DerivePrivateKey.bip32derivePrvKey(rootKey, keyIndex, (byte) 0, 1);\r
byte[] deriveXpub = DeriveXpub.deriveXpub(derivePrivateKey);\r
\r
String issueProgram = "ae20" + Hex.toHexString(deriveXpub).substring(0, 64) + "5151ad";\r
--- /dev/null
+package io.bytom.types;
+
+import java.util.Map;
+
+public abstract class InputEntry extends Entry {
+
+ public int ordinal;
+
+ public abstract void setDestination(Hash id, long pos, Map<Hash, Entry> entryMap);
+}
package io.bytom.types;\r
\r
import java.io.ByteArrayOutputStream;\r
+import java.util.Map;\r
\r
-public class Issue extends Entry {\r
+public class Issue extends InputEntry {\r
public Hash nonceHash;\r
public AssetAmount assetAmount;\r
- public int ordinal;\r
public AssetDefinition assetDefinition;\r
public ValueDestination witnessDestination;\r
\r
this.ordinal = ordinal;\r
}\r
\r
- public void setDestination(Hash id, AssetAmount val, long pos) {\r
- this.witnessDestination = new ValueDestination(id, val, pos);\r
+ @Override\r
+ public void setDestination(Hash id, long pos, Map<Hash, Entry> entryMap) {\r
+ this.witnessDestination = new ValueDestination(id, this.assetAmount, pos);\r
}\r
\r
@Override\r
\r
import java.io.ByteArrayOutputStream;\r
\r
-public class Output extends Entry {\r
+public class OutputEntry extends Entry {\r
\r
public ValueSource source;\r
\r
\r
public Integer ordinal;\r
\r
- public Output() {\r
+ public OutputEntry() {\r
this.source = new ValueSource();\r
this.controlProgram = new Program();\r
}\r
\r
\r
- public Output(ValueSource source, Program controlProgram, Integer ordinal) {\r
+ public OutputEntry(ValueSource source, Program controlProgram, Integer ordinal) {\r
this.source = source;\r
this.controlProgram = controlProgram;\r
this.ordinal = ordinal;\r
package io.bytom.types;\r
\r
import java.io.ByteArrayOutputStream;\r
+import java.util.Map;\r
\r
-public class Spend extends Entry {\r
+public class Spend extends InputEntry {\r
\r
public Hash spentOutputID;\r
\r
this.ordinal = ordinal;\r
}\r
\r
- public void setDestination(Hash id, AssetAmount val, long pos) {\r
- this.witnessDestination = new ValueDestination(id, val, pos);\r
+ @Override\r
+ public void setDestination(Hash id, long pos, Map<Hash, Entry> entryMap) {\r
+ OutputEntry spendOutput = (OutputEntry) entryMap.get(this.spentOutputID);\r
+ this.witnessDestination = new ValueDestination(id, spendOutput.source.value, pos);\r
}\r
\r
@Override\r
if (change) {\r
changeStr = "01000000";\r
}\r
- byte[][] paths = new byte[][]{\r
+ return new byte[][]{\r
Hex.decode("2c000000"),\r
Hex.decode("99000000"),\r
Hex.decode(accountIndexStr),\r
Hex.decode(changeStr),\r
Hex.decode(programIndexStr),\r
};\r
- return paths;\r
}\r
\r
- public static byte[][] getBip32Path(int accountIndex) {\r
+ public static byte[][] getBip32Path(byte keySpace, long accountIndex, long ...itemIndexes) {\r
byte[] signerPath = new byte[9];\r
- byte[] path = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putInt(accountIndex).array();\r
+ signerPath[0] = keySpace;\r
+ byte[] path = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(accountIndex).array();\r
System.arraycopy(path, 0, signerPath, 1, 8);\r
- byte[][] paths = new byte[][]{\r
- signerPath\r
- };\r
- return paths;\r
+\r
+ byte[][] res = new byte[1 + itemIndexes.length][];\r
+ res[0] = signerPath;\r
+ for (int i = 0; i < itemIndexes.length; i++) {\r
+ path = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(itemIndexes[i]).array();\r
+ res[i + 1] = path;\r
+ }\r
+ return res;\r
}\r
}\r
package io.bytom;\r
\r
import io.bytom.api.*;\r
-import io.bytom.exception.BytomException;\r
-import io.bytom.http.Client;\r
import org.bouncycastle.util.encoders.Hex;\r
import org.junit.Test;\r
\r
-import java.security.InvalidKeyException;\r
-import java.security.NoSuchAlgorithmException;\r
-import java.security.SignatureException;\r
-\r
public class AppTest {\r
String rootKey = "38d2c44314c401b3ea7c23c54e12c36a527aee46a7f26b82443a46bf40583e439dea25de09b0018b35a741d8cd9f6ec06bc11db49762617485f5309ab72a12d4";\r
String btmAssetID = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";\r
\r
\r
@Test\r
- public void testSpend() throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {\r
- Transaction.AnnotatedInput input = btmUtxoToInput();\r
- Transaction Transaction = new Transaction.Builder()\r
-// .addInput(new Transaction.AnnotatedInput().setType(1).setAssetId(btmAssetID).setAmount(880000000).setControlProgram("001414d362694eacfa110dc20dec77d610d22340f95b").\r
-// setChange(false).setControlProgramIndex(2).setSourceId("fc43933d1c601b2503b033e31d3bacfa5c40ccb2ff0be6e94d8332462e0928a3").setSourcePosition(0))\r
- .addInput(input.setType(1))\r
- .addOutput(new Transaction.AnnotatedOutput().setAssetId(btmAssetID).setAmount(49100000).setControlProgram("001414d362694eacfa110dc20dec77d610d22340f95b"))\r
- .addOutput(new Transaction.AnnotatedOutput().setAssetId(btmAssetID).setAmount(10000000).setControlProgram("0020fa56ca7d47f8528e68e120d0e052885faeb9d090d238fa4266bdde21b137513c"))\r
- .build(200000);\r
- io.bytom.api.Transaction transaction = MapTransaction.mapTx(Transaction);\r
- SignTransaction signTransaction = new SignTransaction();\r
- String rawTransaction = signTransaction.rawTransaction(rootKey, transaction);\r
- System.out.println(rawTransaction);\r
+ public void testSpendBIP44() {\r
+ SpendInput input = new SpendInput(btmAssetID, 9800000000L, "0014cb9f2391bafe2bc1159b2c4c8a0f17ba1b4dd94e");\r
+ input.setSourcePosition(2);\r
+ input.setSourceId("4b5cb973f5bef4eadde4c89b92ee73312b940e84164da0594149554cc8a2adea");\r
+ input.setChange(true);\r
+ input.setControlProgramIndex(457);\r
+ input.setKeyIndex(1);\r
+ input.setRootPrivateKey("4864bae85cf38bfbb347684abdbc01e311a24f99e2c7fe94f3c071d9c83d8a5a349722316972e382c339b79b7e1d83a565c6b3e7cf46847733a47044ae493257");\r
+\r
+ Transaction tx = new Transaction.Builder()\r
+ .addInput(input)\r
+ .addOutput(new Output(btmAssetID, 8800000000L, "0014a82f02bc37bc5ed87d5f9fca02f8a6a7d89cdd5c"))\r
+ .addOutput(new Output(btmAssetID, 900000000L, "00200824e931fb806bd77fdcd291aad3bd0a4493443a4120062bd659e64a3e0bac66"))\r
+ .setTimeRange(0)\r
+ .build();\r
+\r
+ String rawTransaction = tx.rawTransaction();\r
+ assert rawTransaction.equals("070100010160015e4b5cb973f5bef4eadde4c89b92ee73312b940e84164da0594149554cc8a2adeaffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80c480c1240201160014cb9f2391bafe2bc1159b2c4c8a0f17ba1b4dd94e6302401cb779288be890a28c5209036da1a27d9fe74a51c38e0a10db4817bcf4fd05f68580239eea7dcabf19f144c77bf13d3674b5139aa51a99ba58118386c190af0e20bcbe020b05e1b7d0825953d92bf47897be08cd7751a37adb95d6a2e5224f55ab02013dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80b095e42001160014a82f02bc37bc5ed87d5f9fca02f8a6a7d89cdd5c000149ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80d293ad03012200200824e931fb806bd77fdcd291aad3bd0a4493443a4120062bd659e64a3e0bac6600");\r
+ }\r
+\r
+ @Test\r
+ public void testSpendBIP32() {\r
+ SpendInput input = new SpendInput(btmAssetID, 11718900000L, "0014085a02ecdf934a56343aa59a3dec9d9feb86ee43");\r
+ input.setSourceId("5ac79a73db78e5c9215b37cb752f0147d1157c542bb4884908ceb97abc33fe0a");\r
+ input.setSourcePosition(0);\r
+ input.setRootPrivateKey("4864bae85cf38bfbb347684abdbc01e311a24f99e2c7fe94f3c071d9c83d8a5a349722316972e382c339b79b7e1d83a565c6b3e7cf46847733a47044ae493257");\r
+ input.setChange(true);\r
+ input.setKeyIndex(1);\r
+ input.setBipProtocol(BIPProtocol.BIP32);\r
+ input.setControlProgramIndex(447);\r
+\r
+ Transaction tx = new Transaction.Builder()\r
+ .addInput(input)\r
+ .addOutput(new Output(btmAssetID, 1718900000L, "001409a0961e9b592a944ca3ded0ef9403fdb25b3793"))\r
+ .addOutput(new Output(btmAssetID, 9900000000L, "00145ade29df622cc68d0473aa1a20fb89690451c66e"))\r
+ .setTimeRange(0)\r
+ .build();\r
+\r
+ String rawTx = tx.rawTransaction();\r
+ assert rawTx.equals("070100010160015e5ac79a73db78e5c9215b37cb752f0147d1157c542bb4884908ceb97abc33fe0affffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0f280d42b0001160014085a02ecdf934a56343aa59a3dec9d9feb86ee43630240e37864ef905e943deb97e58b861c97f04f07c1d33b1ce4f1b6edddff0c8956a57d86fc922c45446cd38ea613fc3c9b7d5a2596f3dca7a7212f6da55b9515110420a29601468f08c57ca9c383d28736a9d5c7737cd483126d8db3d85490fe497b3502013dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0aad1b3060116001409a0961e9b592a944ca3ded0ef9403fdb25b379300013dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8086d8f024011600145ade29df622cc68d0473aa1a20fb89690451c66e00");\r
}\r
\r
//issue asset\r
@Test\r
- public void testIssue() throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {\r
-\r
- String issueAssetId = "a680606d49daae62ef9cb03263ca82a0b1e3184bb6311ea52a5189207f718789";\r
- String program = "ae204cae24c2cec15491e70fc554026469496e373df9b9970b23acac8b782da0822d5151ad";\r
- String assetDefine = "7b0a202022646563696d616c73223a20382c0a2020226465736372697074696f6e223a207b7d2c0a2020226e616d65223a2022222c0a20202273796d626f6c223a2022220a7d";\r
- Transaction.AnnotatedInput input = btmUtxoToInput();\r
- Transaction Transaction = new Transaction.Builder()\r
- .addInput(new Transaction.AnnotatedInput().setType(0).setAssetId(issueAssetId).setControlProgram(program).setAmount(100000000).setAssetDefinition(assetDefine).setChange(false).setKeyIndex(13))\r
-// .addInput(new Transaction.AnnotatedInput().setType(1).setAssetId(btmAssetID).setAmount(880000000).setControlProgram("001414d362694eacfa110dc20dec77d610d22340f95b").\r
-// setChange(false).setControlProgramIndex(2).setSourceId("fc43933d1c601b2503b033e31d3bacfa5c40ccb2ff0be6e94d8332462e0928a3").setSourcePosition(0))\r
- .addInput(input.setType(1))\r
- .addOutput(new Transaction.AnnotatedOutput().setAssetId(issueAssetId).setAmount(100000000).setControlProgram("001414d362694eacfa110dc20dec77d610d22340f95b"))\r
-// .addOutput(new Transaction.AnnotatedOutput().setAssetId(btmAssetID).setAmount(870000000).setControlProgram("001414d362694eacfa110dc20dec77d610d22340f95b"))\r
- .build(2000000);\r
- MapTransaction.mapTx(Transaction);\r
- SignTransaction sign = new SignTransaction();\r
- String rawTransaction = sign.rawTransaction(rootKey, Transaction);\r
- System.out.println(rawTransaction);\r
+ public void testIssue() {\r
+ IssuanceInput issuanceInput = new IssuanceInput();\r
+ issuanceInput.setAssetId("7b38dc897329a288ea31031724f5c55bcafec80468a546955023380af2faad14");\r
+ issuanceInput.setAmount(100000000000L);\r
+ issuanceInput.setProgram("ae2054a71277cc162eb3eb21b5bd9fe54402829a53b294deaed91692a2cd8a081f9c5151ad");\r
+ issuanceInput.setNonce("ac9d5a527f5ab00a");\r
+ issuanceInput.setKeyIndex(5);\r
+ issuanceInput.setAssetDefinition("7b0a202022646563696d616c73223a20382c0a2020226465736372697074696f6e223a207b7d2c0a2020226e616d65223a2022222c0a20202273796d626f6c223a2022220a7d");\r
+ issuanceInput.setRootPrivateKey("4864bae85cf38bfbb347684abdbc01e311a24f99e2c7fe94f3c071d9c83d8a5a349722316972e382c339b79b7e1d83a565c6b3e7cf46847733a47044ae493257");\r
+\r
+ SpendInput spendInput = new SpendInput(btmAssetID, 9800000000L, "0014cb9f2391bafe2bc1159b2c4c8a0f17ba1b4dd94e");\r
+ spendInput.setBipProtocol(BIPProtocol.BIP32);\r
+ spendInput.setKeyIndex(1);\r
+ spendInput.setChange(true);\r
+ spendInput.setSourceId("4b5cb973f5bef4eadde4c89b92ee73312b940e84164da0594149554cc8a2adea");\r
+ spendInput.setSourcePosition(2);\r
+ spendInput.setControlProgramIndex(457);\r
+ spendInput.setRootPrivateKey("4864bae85cf38bfbb347684abdbc01e311a24f99e2c7fe94f3c071d9c83d8a5a349722316972e382c339b79b7e1d83a565c6b3e7cf46847733a47044ae493257");\r
+\r
+ Transaction tx = new Transaction.Builder()\r
+ .addInput(issuanceInput)\r
+ .addInput(spendInput)\r
+ .addOutput(new Output("7b38dc897329a288ea31031724f5c55bcafec80468a546955023380af2faad14", 100000000000L, "001437e1aec83a4e6587ca9609e4e5aa728db7007449"))\r
+ .addOutput(new Output(btmAssetID, 9700000000L, "00148be1104e04734e5edaba5eea2e85793896b77c56"))\r
+ .setTimeRange(0)\r
+ .build();\r
+\r
+ String rawTx = tx.rawTransaction();\r
+ assert rawTx.equals("0701000201300008ac9d5a527f5ab00a7b38dc897329a288ea31031724f5c55bcafec80468a546955023380af2faad1480d0dbc3f402b001467b0a202022646563696d616c73223a20382c0a2020226465736372697074696f6e223a207b7d2c0a2020226e616d65223a2022222c0a20202273796d626f6c223a2022220a7d0125ae2054a71277cc162eb3eb21b5bd9fe54402829a53b294deaed91692a2cd8a081f9c5151ad01401ba3a3f2d3e9887e20da8d43666cc1602bb5421ffea9d2b4d7df9816fc174a43b25fb00db4775b10b679a8b5f8aa28555ebb8fb49f6275d43283daf8f5ac340d0160015e4b5cb973f5bef4eadde4c89b92ee73312b940e84164da0594149554cc8a2adeaffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80c480c1240201160014cb9f2391bafe2bc1159b2c4c8a0f17ba1b4dd94e630240341c875fc815dc13ad811c9aa7c7af28a5c78765f77fd5a36dac263278fd605ae0553e34a1e645148370b7b397142ddbcd67ba33483279ab9894169b51e4f101201381d35e235813ad1e62f9a602c82abee90565639cc4573568206b55bcd2aed902013e7b38dc897329a288ea31031724f5c55bcafec80468a546955023380af2faad1480d0dbc3f4020116001437e1aec83a4e6587ca9609e4e5aa728db700744900013dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8082a99124011600148be1104e04734e5edaba5eea2e85793896b77c5600");\r
}\r
\r
\r
//retire asset\r
@Test\r
- public void testRetire() throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {\r
+ public void testRetire() {\r
String arbitrary = "77656c636f6d65efbc8ce6aca2e8bf8ee69da5e588b0e58e9fe5ad90e4b896e7958c";\r
String retireControlProgram = "6a"+Integer.toString(Hex.decode(arbitrary).length,16)+arbitrary;\r
String assetId1 = "207265909236260b30942a6b00e30ceb769e0e58156b6482bac64117619c9dcf";\r
+\r
+ SpendInput input1 = new SpendInput(btmAssetID, 289100000, "0014f1dc52048f439ac7fd74f8106a21da78f00de48f");\r
+ input1.setRootPrivateKey(rootKey);\r
+ input1.setChange(true);\r
+ input1.setControlProgramIndex(41);\r
+ input1.setSourceId("0b2cff11d1d056d95237a5f2d06059e5395e86f60e69c1e8201ea624911c0c65");\r
+ input1.setSourcePosition(0);\r
+\r
+ SpendInput input2 = new SpendInput(assetId1, 70000000000L, "0014bb8a039726df1b649738e9973db14a4b4fd4becf");\r
+ input2.setRootPrivateKey(rootKey);\r
+ input2.setChange(true);\r
+ input2.setControlProgramIndex(26);\r
+ input2.setSourceId("be0ac837e832c34a02968e54dab4f95cbeceb9fb01cd378310f6ea32219ee29b");\r
+ input2.setSourcePosition(1);\r
+\r
+ Output output1 = new Output(btmAssetID, 279100000, "001414d362694eacfa110dc20dec77d610d22340f95b");\r
+ Output output2 = new Output(assetId1, 10000000000L, retireControlProgram);\r
+ Output output3 = new Output(assetId1, 60000000000L, "0014bb8a039726df1b649738e9973db14a4b4fd4becf");\r
+\r
Transaction transaction = new Transaction.Builder()\r
- .addInput(new Transaction.AnnotatedInput().setType(1).setAssetId(btmAssetID).setAmount(289100000).setControlProgram("0014f1dc52048f439ac7fd74f8106a21da78f00de48f").\r
- setChange(true).setControlProgramIndex(41).setSourceId("0b2cff11d1d056d95237a5f2d06059e5395e86f60e69c1e8201ea624911c0c65").setSourcePosition(0))\r
- .addInput(new Transaction.AnnotatedInput().setType(1).setAssetId(assetId1).setAmount(70000000000l).setControlProgram("0014bb8a039726df1b649738e9973db14a4b4fd4becf").\r
- setChange(true).setControlProgramIndex(26).setSourceId("be0ac837e832c34a02968e54dab4f95cbeceb9fb01cd378310f6ea32219ee29b").setSourcePosition(1))\r
- .addOutput(new Transaction.AnnotatedOutput().setAssetId(btmAssetID).setAmount(279100000).setControlProgram("001414d362694eacfa110dc20dec77d610d22340f95b"))\r
- .addOutput(new Transaction.AnnotatedOutput().setAssetId(assetId1).setAmount(10000000000l).setControlProgram(retireControlProgram))\r
- .addOutput(new Transaction.AnnotatedOutput().setAssetId(assetId1).setAmount(60000000000l).setControlProgram("0014bb8a039726df1b649738e9973db14a4b4fd4becf"))\r
- .build(2000000);\r
- MapTransaction.mapTx(transaction);\r
- SignTransaction sign = new SignTransaction();\r
- String rawTransaction = sign.rawTransaction(rootKey, transaction);\r
- System.out.println(rawTransaction);\r
+ .addInput(input1)\r
+ .addInput(input2)\r
+ .addOutput(output1)\r
+ .addOutput(output2)\r
+ .addOutput(output3)\r
+ .setTimeRange(2000000)\r
+ .build();\r
+\r
+ String rawTransaction = transaction.rawTransaction();\r
+ assert rawTransaction.equals("070180897a020160015e0b2cff11d1d056d95237a5f2d06059e5395e86f60e69c1e8201ea624911c0c65ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0a1ed89010001160014f1dc52048f439ac7fd74f8106a21da78f00de48f6302401660121218ab96d9f22cce712541ca34c53f4da40450669854341ca9624ad1cf10d1bfc96449fad5406224afd253ccfbdeab683f7ec7f9ee8f45e47a0c58500f2031ecc1bdd5fb9b40016358340b87646ea39faf55c0c105205cfdfdc6184725f40161015fbe0ac837e832c34a02968e54dab4f95cbeceb9fb01cd378310f6ea32219ee29b207265909236260b30942a6b00e30ceb769e0e58156b6482bac64117619c9dcf80f8cce284020101160014bb8a039726df1b649738e9973db14a4b4fd4becf630240d7b7f1c2ca1048fd6798234f2a1e895762f83e802507a008eff52605611b67390a74eaf228b76f5589ff109b2c20eaa65fad6de2e5ab8a25b54267b607df970b20a71547e1064b5edaad92cdce6b0ace832836ba28fdeaf0b83010bed247fe927c03013dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0f48a85010116001414d362694eacfa110dc20dec77d610d22340f95b00014b207265909236260b30942a6b00e30ceb769e0e58156b6482bac64117619c9dcf80c8afa02501246a2277656c636f6d65efbc8ce6aca2e8bf8ee69da5e588b0e58e9fe5ad90e4b896e7958c00013e207265909236260b30942a6b00e30ceb769e0e58156b6482bac64117619c9dcf80b09dc2df0101160014bb8a039726df1b649738e9973db14a4b4fd4becf00");\r
}\r
\r
\r
//utxo\r
- private Transaction.AnnotatedInput btmUtxoToInput() {\r
+ private SpendInput btmUtxoToInput() {\r
String utxoJson = "\n" +\r
"{\n" +\r
" \"id\": \"687e3c3ca1ee8139e57f43697db6aaeac95b10c75b828ef2fad30abe7d047e6a\",\n" +\r
" \"derive_rule\": 0\n" +\r
"}";\r
UTXO utxo = UTXO.fromJson(utxoJson);\r
- Transaction.AnnotatedInput input = UTXO.utxoToAnnotatedInput(utxo);\r
- return input;\r
- }\r
-\r
- private Transaction.AnnotatedInput otherAssetUtxoToInput() {\r
- String utxoJson = "\n" +\r
- "{\n" +\r
- " \"id\": \"a1b729714a9ad74108999b6d2f3628c6eea2bd41d7132fca93f56c3f7c12904e\",\n" +\r
- " \"amount\": 89900000000,\n" +\r
- " \"address\": \"tm1qstadkjjxp7nn3eegdxc38nvur82z37ek2pq2ul\",\n" +\r
- " \"program\": \"001482fadb4a460fa738e72869b113cd9c19d428fb36\",\n" +\r
- " \"change\": true,\n" +\r
- " \"highest\": 139905,\n" +\r
- " \"account_alias\": \"wyjbtm\",\n" +\r
- " \"asset_id\": \"207265909236260b30942a6b00e30ceb769e0e58156b6482bac64117619c9dcf\",\n" +\r
- " \"asset_alias\": \"TEST3\",\n" +\r
- " \"account_id\": \"0NNSS39M00A02\",\n" +\r
- " \"control_program_index\": 32,\n" +\r
- " \"source_id\": \"2886e635442f2f003e1d211f3264520ffb5239944d718bc834925a0cc0798980\",\n" +\r
- " \"source_pos\": 1,\n" +\r
- " \"valid_height\": 0,\n" +\r
- " \"derive_rule\": 0\n" +\r
- "}";\r
- UTXO utxo = UTXO.fromJson(utxoJson);\r
- Transaction.AnnotatedInput input = UTXO.utxoToAnnotatedInput(utxo);\r
- return input;\r
- }\r
-\r
- // submit rawTransaction\r
- @Test\r
- public void SubmitTransaction() throws BytomException {\r
- Client client = TestUtil.generateClient();\r
- String raw = "070180897a020160015e0b2cff11d1d056d95237a5f2d06059e5395e86f60e69c1e8201ea624911c0c65ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0a1ed89010001160014f1dc52048f439ac7fd74f8106a21da78f00de48f6302401660121218ab96d9f22cce712541ca34c53f4da40450669854341ca9624ad1cf10d1bfc96449fad5406224afd253ccfbdeab683f7ec7f9ee8f45e47a0c58500f2031ecc1bdd5fb9b40016358340b87646ea39faf55c0c105205cfdfdc6184725f40161015fbe0ac837e832c34a02968e54dab4f95cbeceb9fb01cd378310f6ea32219ee29b207265909236260b30942a6b00e30ceb769e0e58156b6482bac64117619c9dcf80f8cce284020101160014bb8a039726df1b649738e9973db14a4b4fd4becf630240d7b7f1c2ca1048fd6798234f2a1e895762f83e802507a008eff52605611b67390a74eaf228b76f5589ff109b2c20eaa65fad6de2e5ab8a25b54267b607df970b20a71547e1064b5edaad92cdce6b0ace832836ba28fdeaf0b83010bed247fe927c03013dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0f48a85010116001414d362694eacfa110dc20dec77d610d22340f95b00014b207265909236260b30942a6b00e30ceb769e0e58156b6482bac64117619c9dcf80c8afa02501246a2277656c636f6d65efbc8ce6aca2e8bf8ee69da5e588b0e58e9fe5ad90e4b896e7958c00013e207265909236260b30942a6b00e30ceb769e0e58156b6482bac64117619c9dcf80b09dc2df0101160014bb8a039726df1b649738e9973db14a4b4fd4becf00";\r
- SubmitTransaction.SubmitResponse submitResponse = SubmitTransaction.submitRawTransaction(client, raw);\r
- System.out.println(submitResponse.tx_id);\r
- }\r
-\r
-\r
- //单输入多签\r
- @Test\r
- public void testMutiSpend(){\r
- Transaction.AnnotatedInput input = btmUtxoToInput();\r
- Transaction Transaction = new Transaction.Builder()\r
-// .addInput(new Transaction.AnnotatedInput().setType(1).setAssetId(btmAssetID).setAmount(880000000).setControlProgram("001414d362694eacfa110dc20dec77d610d22340f95b").\r
-// setChange(false).setControlProgramIndex(2).setSourceId("fc43933d1c601b2503b033e31d3bacfa5c40ccb2ff0be6e94d8332462e0928a3").setSourcePosition(0))\r
- .addInput(input.setType(1))\r
- .addOutput(new Transaction.AnnotatedOutput().setAssetId(btmAssetID).setAmount(80000000).setControlProgram("00204d505f3bb98a6022fa37e387204dba3b2917cf6df4d41c5622485c55b1b17813"))\r
- .addOutput(new Transaction.AnnotatedOutput().setAssetId(btmAssetID).setAmount(10000000).setControlProgram("001414d362694eacfa110dc20dec77d610d22340f95b"))\r
- .build(200000);\r
- Transaction transaction = MapTransaction.mapTx(Transaction);\r
- SignTransaction signTransaction = new SignTransaction();\r
- String[] rootKeys = new String[3];\r
- rootKeys[0] = "38d2c44314c401b3ea7c23c54e12c36a527aee46a7f26b82443a46bf40583e439dea25de09b0018b35a741d8cd9f6ec06bc11db49762617485f5309ab72a12d4";\r
- rootKeys[1] = "50a23bf6200b8a98afc049a7d0296a619e2ee27fa0d6d4d271ca244b280b324347627e543cc079614642c7b88c78ce38092430b01d124663e8b84026aefefde1";\r
- rootKeys[2] = "00e4bf1251fb5aa37aa2a11dec6c0db5cec3f17aa312dbddb30e06957a32ae503ebcdfd4ad5e29be21ee9ec336e939eb72439cf6d99c785268c8f3d71c1be877";\r
- signTransaction.buildWitness(transaction, 0, rootKeys);\r
- String raw = signTransaction.serializeTransaction(transaction);\r
- System.out.println(raw);\r
+ return utxo.toSpendAnnotatedInput();\r
}\r
}\r
package io.bytom.api;\r
\r
-import io.bytom.common.DerivePrivateKey;\r
-import io.bytom.common.DeriveXpub;\r
-import io.bytom.common.ExpandedPrivateKey;\r
import org.bouncycastle.util.encoders.Hex;\r
import org.junit.Test;\r
\r
String childXprv = "e8c0965af60563c4cabcf2e947b1cd955c4f501eb946ffc8c3447e5ec8a6335398a3720b3f96077fa187fdde48fe7dc293984b196f5e292ef8ed78fdbd8ed954";\r
String expandedXprv = "20849f14bbe212d1b8917da2e1eda9afc4c21e9dd0a47f1e169a3326d45ae443236f54b987369e86ed78eb2b0a2def89a69ec69ca1059e2efe045796dc583d91";\r
String hashedMessage = "99ab9ebdba106466371467b036d56a0e54ad2a6035e365a6103ba97ab553fd52";\r
- byte[] sig = Signer.Ed25519InnerSign(Hex.decode(expandedXprv), Hex.decode(hashedMessage));\r
+ byte[] sig = Signer.ed25519InnerSign(Hex.decode(expandedXprv), Hex.decode(hashedMessage));\r
System.out.println("sig:" + Hex.toHexString(sig));\r
//expected: e628e980c690d9ef4ca8a2edee1654a6b401edc4f1af7bda3ffd97fe412522c3bab671dd4e51d0aeeb64f8d761fbdb03e296ab0c1dcbed4eafa504f412a98100\r
}\r
@Test\r
public void testBip44Key() throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {\r
String rootKey = "38d2c44314c401b3ea7c23c54e12c36a527aee46a7f26b82443a46bf40583e439dea25de09b0018b35a741d8cd9f6ec06bc11db49762617485f5309ab72a12d4";\r
- byte[] derivePrivateKey = DerivePrivateKey.derivePrivateKey(rootKey, 1, false, 2);\r
+ byte[] derivePrivateKey = DerivePrivateKey.bip44derivePrvKey(rootKey, 1, false, 2);\r
System.out.println(Hex.toHexString(derivePrivateKey));\r
//expected 48c65f40d860723e71b03988a22edc9ad00ae0deae992e79fb3b812edb5c3e43e78065bf46d0e8ad922cdae600fd2c2a6239b8f1f504f8f255460c6fcce023ff\r
}\r
@Test\r
public void testBip32Key() throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {\r
String rootKey = "38d2c44314c401b3ea7c23c54e12c36a527aee46a7f26b82443a46bf40583e439dea25de09b0018b35a741d8cd9f6ec06bc11db49762617485f5309ab72a12d4";\r
- byte[] derivePrivateKey = DerivePrivateKey.derivePrivateKey(rootKey, 2);\r
+ byte[] derivePrivateKey = DerivePrivateKey.bip32derivePrvKey(rootKey, 2, (byte) 0);\r
System.out.println(Hex.toHexString(derivePrivateKey));\r
//expected 2806f655e862ffc550c2dcaa5057e02e0a1c7c714a4c5a1823bd83f060593e43f5fef4fced766de36cb7ea8ca51cebac7830d54dca1929e669a4a7c3dc7b9dcc\r
}\r
@Test\r
public void testBip32PublicKey() throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {\r
String rootKey = "38d2c44314c401b3ea7c23c54e12c36a527aee46a7f26b82443a46bf40583e439dea25de09b0018b35a741d8cd9f6ec06bc11db49762617485f5309ab72a12d4";\r
- byte[] derivePrivateKey = DerivePrivateKey.derivePrivateKey(rootKey, 14);\r
+ byte[] derivePrivateKey = DerivePrivateKey.bip32derivePrvKey(rootKey, 14, (byte) 0);\r
byte[] deriveXpub = DeriveXpub.deriveXpub(derivePrivateKey);\r
System.out.println(Hex.toHexString(deriveXpub).substring(0, 64));\r
//expected 2806f655e862ffc550c2dcaa5057e02e0a1c7c714a4c5a1823bd83f060593e43f5fef4fced766de36cb7ea8ca51cebac7830d54dca1929e669a4a7c3dc7b9dcc\r
@Test\r
public void testExpandedKey() throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {\r
String childXprv = "e8c0965af60563c4cabcf2e947b1cd955c4f501eb946ffc8c3447e5ec8a6335398a3720b3f96077fa187fdde48fe7dc293984b196f5e292ef8ed78fdbd8ed954";\r
- byte[] z = ExpandedPrivateKey.ExpandedPrivateKey(Hex.decode(childXprv));\r
+ byte[] z = ExpandedPrivateKey.expandedPrivateKey(Hex.decode(childXprv));\r
System.out.println(Hex.toHexString(z));\r
//expect: e8c0965af60563c4cabcf2e947b1cd955c4f501eb946ffc8c3447e5ec8a633535b899d45316cd83e027913d3ff3dc52f6a951a686fd2b750099e1f7c70cb98c3\r
// e8c0965af60563c4cabcf2e947b1cd955c4f501eb946ffc8c3447e5ec8a633535b899d45316cd83e027913d3ff3dc52f6a951a686fd2b750099e1f7c70cb98c3\r
for (int i = 0; i < hpaths.length; i++) {\r
byte[] xpub = DeriveXpub.deriveXpub(res);\r
// System.out.println("xpub: "+Hex.toHexString(xpub));\r
- res = NonHardenedChild.NHchild(paths[i], res, xpub);\r
+ res = NonHardenedChild.nhChild(paths[i], res, xpub);\r
}\r
- System.out.println("res: " + Hex.toHexString(res));\r
//expected: e8c0965af60563c4cabcf2e947b1cd955c4f501eb946ffc8c3447e5ec8a6335398a3720b3f96077fa187fdde48fe7dc293984b196f5e292ef8ed78fdbd8ed954\r
// e8c0965af60563c4cabcf2e947b1cd955c4f501eb946ffc8c3447e5ec8a6335398a3720b3f96077fa187fdde48fe7dc293984b196f5e292ef8ed78fdbd8ed954\r
}\r
String hxprv = "10fdbc41a4d3b8e5a0f50dd3905c1660e7476d4db3dbd9454fa4347500a633531c487e8174ffc0cfa76c3be6833111a9b8cd94446e37a76ee18bb21a7d6ea66b";\r
String[] hpaths = {"010400000000000000", "0100000000000000"};\r
byte[] childXprv = NonHardenedChild.child(Hex.decode(hxprv), hpaths);\r
- System.out.println("childXprv: " + Hex.toHexString(childXprv));\r
//expected: e8c0965af60563c4cabcf2e947b1cd955c4f501eb946ffc8c3447e5ec8a6335398a3720b3f96077fa187fdde48fe7dc293984b196f5e292ef8ed78fdbd8ed954\r
// e8c0965af60563c4cabcf2e947b1cd955c4f501eb946ffc8c3447e5ec8a6335398a3720b3f96077fa187fdde48fe7dc293984b196f5e292ef8ed78fdbd8ed954\r
}\r