diff --git a/com/raugfer/crypto/coins.java b/com/raugfer/crypto/coins.java index 30bf584..3249b2c 100644 --- a/com/raugfer/crypto/coins.java +++ b/com/raugfer/crypto/coins.java @@ -579,7 +579,7 @@ public class coins { attrs.put("transaction.version", 0x80000004); attrs.put("transaction.groupid", 0x892f2085); attrs.put("signature.hashing", "blake2b256"); - attrs.put("signature.hashing.prefix", bytes.concat("ZcashSigHash".getBytes(), new byte[]{ (byte)0xbb, (byte)0x09, (byte)0xb8, (byte)0x76 })); + attrs.put("signature.hashing.prefix", bytes.concat("ZcashSigHash".getBytes(), new byte[]{ (byte)0x60, (byte)0x0e, (byte)0xb4, (byte)0x2b })); attrs.put("sighash.method", "sapling"); attrs.put("confirmations", 20); attrs.put("block.time", 5 * 30); @@ -587,6 +587,38 @@ public class coins { coins.put("zcash", attrs); } + static { + dict attrs = new dict(); + attrs.put("overloads", "neo"); + attrs.put("transaction.format", "ontologytx"); + attrs.put("signature.hashing", "sha256hash256"); + attrs.put("confirmations", 10); + attrs.put("block.time", 15); + attrs.put("decimals", 0); + attrs.put("transfer.gaslimit", 20000); + attrs.put("transfer.gasprice", 500); + attrs.put("contract.address", new byte[]{ + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01 + }); + coins.put("ontology", attrs); + } + + static { + dict attrs = new dict(); + attrs.put("overloads", "ontology"); + attrs.put("decimals", 9); + attrs.put("contract.address", new byte[]{ + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02 + }); + coins.put("ontologygas", attrs); + } + static { dict attrs = new dict(); attrs.put("overloads", "ethereum"); diff --git a/com/raugfer/crypto/nist256p1.java b/com/raugfer/crypto/nist256p1.java index abb1032..5a87160 100644 --- a/com/raugfer/crypto/nist256p1.java +++ b/com/raugfer/crypto/nist256p1.java @@ -136,7 +136,7 @@ public static BigInteger[] rec(BigInteger h, Object[] S) { boolean odd = (boolean) S[2]; if (!rng(r)) throw new IllegalArgumentException("Out of range"); if (!rng(s)) throw new IllegalArgumentException("Out of range"); - if (s.compareTo(n.shiftRight(2)) > 0) throw new IllegalArgumentException("Out of range"); + if (s.compareTo(n.shiftRight(1)) > 0) throw new IllegalArgumentException("Out of range"); BigInteger[] R = { r, fnd(r, odd) }; BigInteger z = h.negate().mod(n); BigInteger invr = r.modPow(n.subtract(BigInteger.valueOf(2)), n); diff --git a/com/raugfer/crypto/secp256k1.java b/com/raugfer/crypto/secp256k1.java index a3f9be1..9951a07 100644 --- a/com/raugfer/crypto/secp256k1.java +++ b/com/raugfer/crypto/secp256k1.java @@ -136,7 +136,7 @@ public static BigInteger[] rec(BigInteger h, Object[] S) { boolean odd = (boolean) S[2]; if (!rng(r)) throw new IllegalArgumentException("Out of range"); if (!rng(s)) throw new IllegalArgumentException("Out of range"); - if (s.compareTo(n.shiftRight(2)) > 0) throw new IllegalArgumentException("Out of range"); + if (s.compareTo(n.shiftRight(1)) > 0) throw new IllegalArgumentException("Out of range"); BigInteger[] R = { r, fnd(r, odd) }; BigInteger z = h.negate().mod(n); BigInteger invr = r.modPow(n.subtract(BigInteger.valueOf(2)), n); diff --git a/com/raugfer/crypto/service.java b/com/raugfer/crypto/service.java index 796ad88..a3a9468 100644 --- a/com/raugfer/crypto/service.java +++ b/com/raugfer/crypto/service.java @@ -5,6 +5,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Random; public class service { @@ -113,6 +114,11 @@ public static BigInteger estimate_fee(Object _source_addresses, BigInteger amoun if (fmt.equals("protobuf")) { return BigInteger.ZERO; } + if (fmt.equals("ontologytx")) { + BigInteger gaslimit = BigInteger.valueOf(coins.attr("transfer.gaslimit", 0, coin, testnet)); + BigInteger gasprice = BigInteger.valueOf(coins.attr("transfer.gasprice", 0, coin, testnet)); + return gaslimit.multiply(gasprice); + } throw new IllegalArgumentException("Unknown format"); } @@ -536,6 +542,27 @@ public static pair create_rawtxn(Object _source_addresses, St byte[] txn = transaction.transaction_encode(fields, coin, testnet); return new pair<>(new byte[][]{ txn }, f); } + if (fmt.equals("ontologytx")) { + String source_address = source_addresses[0]; + context f = (lookup) -> lookup.call(source_address); + int gas = coins.attr("transfer.gaslimit", coin, testnet); + BigInteger gaslimit = BigInteger.valueOf(gas); + BigInteger gasprice = fee.divide(gaslimit); + BigInteger nonce = BigInteger.valueOf(BigInteger.valueOf(new Random().nextInt()).intValue()).abs(); + byte[] contract = coins.attr("contract.address", new byte[]{}, coin, testnet); + dict fields = new dict(); + fields.put("nonce", nonce); + fields.put("gasprice", gasprice); + fields.put("gaslimit", gaslimit); + fields.put("payer", source_addresses.length > 1? source_addresses[1] : source_addresses[0]); + fields.put("from", source_addresses[0]); + fields.put("to", address); + fields.put("amount", amount); + fields.put("contract", contract); + byte[] txn = transaction.transaction_encode(fields, coin, testnet); + return new pair<>(new byte[][]{txn}, f); + + } throw new IllegalArgumentException("Unknown format"); } diff --git a/com/raugfer/crypto/signing.java b/com/raugfer/crypto/signing.java index b068eec..5de008f 100644 --- a/com/raugfer/crypto/signing.java +++ b/com/raugfer/crypto/signing.java @@ -122,6 +122,7 @@ public static byte[] signature_create(String privatekey, byte[] data, BigInteger case "sha512h": b = hashing.sha512h(bytes.concat(prefix, data)); break; case "blake1s": b = hashing.blake1s(bytes.concat(prefix, data)); break; case "blake2b256": b = hashing.blake2b(data, prefix, 32); break; + case "sha256hash256": b = hashing.sha256(hashing.hash256(bytes.concat(prefix, data))); break; default: throw new IllegalStateException("Unknown hash function"); } byte[] envelop_prefix = coins.attr("signature.hashing.envelop.prefix", new byte[]{ }, coin, testnet); diff --git a/com/raugfer/crypto/transaction.java b/com/raugfer/crypto/transaction.java index bcf3d7b..e305c13 100644 --- a/com/raugfer/crypto/transaction.java +++ b/com/raugfer/crypto/transaction.java @@ -504,6 +504,72 @@ private static byte[] neoinout_script_encode(dict fields) { return bytes.concat(b1, b2, b3, b4); } + private static byte[] int_neo_bytes(BigInteger data){ + if (data.equals(BigInteger.ZERO)) { + return new byte[0]; + } + byte[] bs = data.toByteArray(); + if(bs.length == 0) { + return new byte[0]; + } + if(data.signum() < 0) { + byte b = data.negate().toByteArray()[0]; + byte[] res = bytes.rev(bs); + if(b >> 7 == 1){ + byte[] t = new byte[res.length + 1]; + System.arraycopy(res,0,t,0,res.length); + t[res.length] = (byte)255; + return t; + } + return res; + }else{ + byte b = bs[0]; + byte[] res = bytes.rev(bs); + if(b >> 7 == 1){ + byte[] t = new byte[res.length + 1]; + System.arraycopy(res,0,t,0,res.length); + t[res.length] = (byte)0; + return t; + } + return res; + } + } + + private static byte[] ont_code_encode(dict fields, String coin, boolean testnet) { + String from = fields.get("from", null); + String to = fields.get("to", null); + BigInteger amount = fields.get("amount", BigInteger.ZERO); + byte[] contract = fields.get("contract", new byte[]{}); + byte[] amountBytes; + if (amount.equals(BigInteger.ONE.negate())) { + amountBytes = new byte[]{0x4f}; + } else if (amount.equals(BigInteger.ZERO)) { + amountBytes = new byte[]{0x00}; + } else if (amount.compareTo(BigInteger.ZERO) > 0 && amount.compareTo(BigInteger.valueOf(16)) <= 0) { + amountBytes = new byte[]{(byte) (0x51 - 1 + amount.byteValue())}; + } else { + amountBytes = int_neo_bytes(amount); + amountBytes = script.OP_PUSHDATA(amountBytes); + } + byte[] fromBytes = binint.n2b(wallet.address_decode(from, coin, testnet).l); + byte[] toBytes = binint.n2b(wallet.address_decode(to, coin, testnet).l); + byte[] initMethod = "transfer".getBytes(); + byte[] nativeInvokeName = "Ontology.Native.Invoke".getBytes(); + byte[] b1 = new byte[]{(byte) 0x00, (byte) 0xc6, (byte) 0x6b}; + byte[] b2 = script.OP_PUSHDATA(fromBytes); + byte[] b3 = new byte[]{(byte) 0x6a, (byte) 0x7c, (byte) 0xc8}; + byte[] b4 = script.OP_PUSHDATA(toBytes); + byte[] b5 = b3; + byte[] b6 = amountBytes; + byte[] b7 = b3; + byte[] b8 = new byte[]{(byte) 0x6c, (byte) 0x51, (byte) 0xc1}; + byte[] b9 = script.OP_PUSHDATA(initMethod); + byte[] b10 = script.OP_PUSHDATA(contract); + byte[] b11 = new byte[]{(byte) 0x00, (byte) 0x68}; + byte[] b12 = script.OP_PUSHDATA(nativeInvokeName); + return bytes.concat(b1, b2, b3, b4, b5, bytes.concat(b6, b7, b8, b9, b10, bytes.concat(b11, b12))); + } + public static byte[] transaction_encode(dict fields, String coin, boolean testnet) { String fmt = coins.attr("transaction.format", coin, testnet); if (fmt.equals("inout")) { @@ -998,6 +1064,44 @@ public static byte[] transaction_encode(dict fields, String coin, boolean testne } return protobuf.dumps(data); } + if (fmt.equals("ontologytx")) { + BigInteger txtype = fields.get("type", BigInteger.valueOf(0xd1)); + BigInteger version = fields.get("version", BigInteger.ZERO); + BigInteger nonce = fields.get("nonce", BigInteger.ZERO); + BigInteger gasprice = fields.get("gasprice", BigInteger.ZERO); + BigInteger gaslimit = fields.get("gaslimit", BigInteger.ZERO); + String payer = fields.get("payer", null); + byte[] sigsBytes = new byte[0]; + if (fields.has("sigs")) { + dict[] sigs = fields.get("sigs"); + byte[] len_sigs = varint(BigInteger.valueOf(sigs.length)); + sigsBytes = bytes.concat(len_sigs, sigsBytes); + byte[] sigdata; + byte[] pubkeys; + for (dict sigobject : sigs) { + sigdata = sigobject.get("sigdata"); + pubkeys = sigobject.get("pubkeys"); + sigdata = script.OP_PUSHDATA(sigdata); + pubkeys = bytes.concat(script.OP_PUSHDATA(pubkeys), script.OP_CHECKSIG); + byte[] len_sigdata = varint(BigInteger.valueOf(sigdata.length)); + byte[] len_pubkeys = varint(BigInteger.valueOf(pubkeys.length)); + sigsBytes = bytes.concat(sigsBytes, len_sigdata, sigdata, len_pubkeys, pubkeys); + } + } + byte[] payerBytes = binint.n2b(wallet.address_decode(payer, coin, testnet).l); + byte[] code = ont_code_encode(fields, coin, testnet); + byte[] b1 = int8(version); + byte[] b2 = int8(txtype); + byte[] b3 = int32(nonce); + byte[] b4 = int64(gasprice); + byte[] b5 = int64(gaslimit); + byte[] b6 = payerBytes; + byte[] b7 = varint(BigInteger.valueOf(code.length)); + byte[] b8 = code; + byte[] b9 = new byte[]{0x00}; + byte[] b10 = sigsBytes; + return bytes.concat(b1, b2, b3, b4, b5, bytes.concat(b6, b7, b8, b9, b10)); + } throw new IllegalStateException("Unknown format"); } @@ -1166,6 +1270,86 @@ private static pair neoinout_script_decode(byte[] txn) { return new pair<>(fields, txn); } + private static BigInteger neo_bytes_int(byte[] ba){ + if(ba.length == 0){ + return BigInteger.ZERO; + } + byte[] bs = bytes.rev(ba); + if(bs[0] >> 7 == 1) { + for(int i = 0;i < bs.length;i++) { + bs[i] = (byte)~bs[i]; + } + BigInteger temp = new BigInteger(bs); + temp.add(BigInteger.ONE); + return temp.negate(); + } + return new BigInteger(bs); + } + + private static dict ont_code_decode(byte[] txn, String coin, boolean testnet) { + txn = bytes.sub(txn, 3); + pair r1 = parse_int8(txn); + int fromSize = r1.l.intValue(); + txn = r1.r; + String from = wallet.address_encode(binint.b2n(bytes.sub(txn, 0, fromSize)), "address", coin, testnet); + txn = bytes.sub(txn, fromSize); + txn = bytes.sub(txn, 3); + pair r2 = parse_int8(txn); + int toSize = r2.l.intValue(); + txn = r2.r; + String to = wallet.address_encode(binint.b2n(bytes.sub(txn, 0, toSize)), "address", coin, testnet); + txn = bytes.sub(txn, toSize); + txn = bytes.sub(txn, 3); + pair r3 = parse_int8(txn); + BigInteger amount = r3.l; + txn = r3.r; + if (Arrays.equals(bytes.sub(txn, 0, 3), new byte[]{(byte) 0x6a, (byte) 0x7c, (byte) 0xc8})) { + if (amount.equals(binint.b2n(new byte[]{(byte) 0x4f}))) { + amount = BigInteger.ONE.negate(); + } else if (amount.equals(binint.b2n(new byte[]{(byte) 0x00}))) { + amount = BigInteger.ZERO; + } else { + amount = amount.subtract(binint.b2n(new byte[]{(byte) 0x51})).add(BigInteger.ONE); + } + } else { + int amountSize = amount.intValue(); + byte[] amountBytes = bytes.sub(txn, 0, amountSize); + amount = neo_bytes_int(amountBytes); + txn = bytes.sub(txn, amountSize); + } + txn = bytes.sub(txn, 3); + txn = bytes.sub(txn, 3); + pair r4 = parse_int8(txn); + int initMethodSize = r4.l.intValue(); + txn = r4.r; + byte[] initMethod = bytes.sub(txn, 0, initMethodSize); + if (!Arrays.equals(initMethod, "transfer".getBytes())) { + throw new IllegalArgumentException("Method error"); + } + txn = bytes.sub(txn, initMethodSize); + pair r5 = parse_int8(txn); + int contractSize = r5.l.intValue(); + txn = r5.r; + byte[] contract = bytes.sub(txn, 0, contractSize); + txn = bytes.sub(txn, contractSize); + txn = bytes.sub(txn, 2); + pair r6 = parse_int8(txn); + int nativeInvokeNameSize = r6.l.intValue(); + txn = r6.r; + byte[] nativeInvokeName = bytes.sub(txn, 0, nativeInvokeNameSize); + if (!Arrays.equals(nativeInvokeName, "Ontology.Native.Invoke".getBytes())) { + throw new IllegalArgumentException("NativeInvokeName error"); + } + txn = bytes.sub(txn, nativeInvokeNameSize); + assert txn.length == 0; + dict fields = new dict(); + fields.put("from", from); + fields.put("to", to); + fields.put("amount", amount); + fields.put("contract", contract); + return fields; + } + public static dict transaction_decode(byte[] txn, String coin, boolean testnet) { String fmt = coins.attr("transaction.format", coin, testnet); if (fmt.equals("inout")) { @@ -1789,6 +1973,82 @@ public static dict transaction_decode(byte[] txn, String coin, boolean testnet) fields.put("amount", message_params.get(3)); return fields; } + if (fmt.equals("ontologytx")) { + if (txn.length < 1) throw new IllegalArgumentException("End of input"); + pair r1 = parse_int8(txn); + BigInteger version = r1.l; + txn = r1.r; + if (!version.equals(BigInteger.ZERO)) throw new IllegalArgumentException("Invalid version"); + if (txn.length < 1) throw new IllegalArgumentException("End of input"); + pair r2 = parse_int8(txn); + BigInteger txType = r2.l; + txn = r2.r; + if (!txType.equals(binint.b2n(new byte[]{(byte) 0xd1}))) throw new IllegalArgumentException("Invalid txtype"); + if (txn.length < 4) throw new IllegalArgumentException("End of input"); + pair r3 = parse_int32(txn); + BigInteger nonce = r3.l; + txn = r3.r; + if (txn.length < 8) throw new IllegalArgumentException("End of input"); + pair r4 = parse_int64(txn); + BigInteger gasprice = r4.l; + txn = r4.r; + if (txn.length < 8) throw new IllegalArgumentException("End of input"); + pair r5 = parse_int64(txn); + BigInteger gaslimit = r5.l; + txn = r5.r; + if (txn.length < 20) throw new IllegalArgumentException("End of input"); + String payer = wallet.address_encode(binint.b2n(bytes.sub(txn, 0, 20)), "address", coin, testnet); + txn = bytes.sub(txn, 20); + if (txn.length < 1) throw new IllegalArgumentException("End of input"); + pair r6 = parse_int8(txn); + int codeSize = r6.l.intValue(); + txn = r6.r; + if (txn.length < codeSize) throw new IllegalArgumentException("End of input"); + dict codeFields = ont_code_decode(bytes.sub(txn, 0, codeSize), coin, testnet); + txn = bytes.sub(txn, codeSize); + pair r7 = parse_int8(txn); + BigInteger attributes = r7.l; + if (!attributes.equals(BigInteger.ZERO)) throw new IllegalArgumentException("Invalid attributes"); + txn = r7.r; + dict fields = new dict(); + fields.put("nonce", nonce); + fields.put("gasprice", gasprice); + fields.put("gaslimit", gaslimit); + fields.put("payer", payer); + fields.put("from", codeFields.get("from")); + fields.put("to", codeFields.get("to")); + fields.put("amount", codeFields.get("amount")); + fields.put("contract", codeFields.get("contract")); + if (txn.length > 0) { + pair t = parse_varint(txn); + BigInteger count = t.l; + txn = t.r; + dict[] sigs = new dict[count.intValue()]; + for (int i = 0; i < sigs.length; i++) { + pair t1 = parse_varint(txn); + txn = t1.r; + pair t2 = parse_varint(txn); + int len_sigdata1 = t2.l.intValue(); + txn = t2.r; + byte[] sigdata = bytes.sub(txn, 0, len_sigdata1); + txn = bytes.sub(txn, len_sigdata1); + pair t3 = parse_varint(txn); + txn = t3.r; + pair t4 = parse_varint(txn); + int len_pubkeys1 = t4.l.intValue(); + txn = t4.r; + byte[] pubkeys = bytes.sub(txn, 0, len_pubkeys1); + txn = bytes.sub(txn, len_pubkeys1 + 1); + dict sigobject = new dict(); + sigobject.put("pubkeys", pubkeys); + sigobject.put("sigdata", sigdata); + sigs[i] = sigobject; + } + fields.put("sigs", sigs); + if (txn.length != 0) throw new IllegalArgumentException("Invalid transaction"); + } + return fields; + } throw new IllegalStateException("Unknown format"); } @@ -1834,6 +2094,11 @@ public static String txnid(byte[] txn, String coin, boolean testnet) { if (fields.has("signature")) fields.del("signature"); txn = transaction_encode(fields, coin, testnet); } + if (txnfmt.equals("ontologytx")) { + dict fields = transaction_decode(txn, coin, testnet); + if (fields.has("sigs")) fields.del("sigs"); + txn = transaction_encode(fields, coin, testnet); + } String fun = coins.attr("transaction.hashing", coin, testnet); byte[] prefix = coins.attr("transaction.hashing.prefix", new byte[]{ }, coin, testnet); byte[] b; @@ -2359,6 +2624,25 @@ public static byte[] transaction_sign(byte[] txn, Object params, String coin, bo fields.put("signature", signature); return transaction_encode(fields, coin, testnet); } + if (fmt.equals("ontologytx")) { + dict fields = transaction_decode(txn, coin, testnet); + if (fields.has("sigs")) fields.del("sigs"); + txn = transaction_encode(fields, coin, testnet); + if (!(params instanceof String[])) params = new String[]{(String) params}; + String[] privatekeys = (String[]) params; + dict[] sigs = new dict[privatekeys.length]; + for (int i = 0; i < privatekeys.length; i++) { + byte[] sigData = signing.signature_create(privatekeys[i], txn, null, coin, testnet); + sigData = bytes.concat(new byte[]{(byte) 0x01}, sigData); + String publickey = wallet.publickey_from_privatekey(privatekeys[i], coin, testnet); + dict sigobject = new dict(); + sigobject.put("pubkeys", binint.h2b(publickey)); + sigobject.put("sigdata", sigData); + sigs[i] = sigobject; + } + fields.put("sigs", sigs); + return transaction_encode(fields, coin, testnet); + } throw new IllegalStateException("Unknown format"); }