/// <summary> Gets shared secret. </summary> /// /// def get_shared_secret(priv, pub) : /// pub_point = pub.point() /// priv_point = int(repr(priv),16) /// res = pub_point * priv_point /// res_hex = '%032x' % res.x() /// return res_hex /// /// <remarks> Paul, 26/10/2015. </remarks> /// /// <returns> The shared secret. </returns> public static byte[] GetSharedSecret(KeyPair priv, PublicKey pub) { var ps = SecNamedCurves.GetByName("secp256k1"); BigInteger ipriv = new BigInteger(1, priv.PrivateKeyBytes); ECPoint pubPoint = pub.GetECPoint(); ECPoint shared = pubPoint.Multiply(ipriv); BigInteger s = shared.X.ToBigInteger(); byte[] data = s.ToByteArray(); int leadingZeros=-1; while(data[++leadingZeros] == 0); byte[] result = new byte[data.Length - leadingZeros]; Buffer.BlockCopy(data, leadingZeros, result, 0, result.Length); return result; }
/// <summary> /// Constructor that calculates the address given one escrow invitation code and one matching payment invitation code. /// They can be provided in any order. /// </summary> public EscrowCodeSet(string code1, string code2) { if (code1 == null || code2 == null || code1 == "" || code2 == "") { throw new ArgumentException("Two codes are required to use this function."); } string escrowInvitationCode = null, paymentInvitationCode = null; if (code1.StartsWith("einva") || code1.StartsWith("einvb")) { escrowInvitationCode = code1; } if (code2.StartsWith("einva") || code2.StartsWith("einvb")) { escrowInvitationCode = code2; } if (code1.StartsWith("einvp")) { paymentInvitationCode = code1; } if (code2.StartsWith("einvp")) { paymentInvitationCode = code2; } if (escrowInvitationCode == null || paymentInvitationCode == null) { throw new ArgumentException("In order to use this function, one code MUST be an Escrow Invitation (starting " + "with \"einva\" or \"einvb\") and the other code MUST be a Payment Invitation (starting with \"einvp\")."); } byte[] pubpart, privpart; int identifier30; string failreason = parseEscrowCode(escrowInvitationCode, out pubpart, out privpart, out identifier30); if (failreason != null) { throw new ArgumentException(failreason); } // Look for first 40 bits being all 0's or all 1's string notvalid = "Not a valid Payment Invitation Code"; string notvalid2 = "Code is not a valid Payment Invitation Code or may have a typo or other error."; string notvalid3 = "The Payment Invitation does not belong to the provided Escrow Invitation."; long head; byte[] invbytes; string failReason = parseEitherCode(paymentInvitationCode, notvalid, notvalid2, out invbytes, out head); if (head < headbaseP) { throw new ArgumentException(notvalid); } long identifier30L = head - headbaseP; if (identifier30L < 0 || identifier30L > 0x3FFFFFFFL) { throw new ArgumentException(notvalid); } if ((long)identifier30 != identifier30L) { throw new ArgumentException(notvalid3); } byte[] privpartz = new byte[32]; Array.Copy(invbytes, 8 + 1 + 1, privpartz, 0, 32); byte networkByte = invbytes[8]; bool compressedFlag = (invbytes[8 + 1 + 1 + 32 + 20] & 0x1) == 1; // get bitcoin address PublicKey pk = new PublicKey(pubpart); ECPoint Gxyz = pk.GetECPoint().Multiply(new BigInteger(1, privpart)).Multiply(new BigInteger(1, privpartz)); // uncompress if compress is not indicated if (compressedFlag == false) { Gxyz = PublicKey.GetUncompressed(Gxyz); } // We can get the Bitcoin address now, so do so PublicKey pkxyz = new PublicKey(Gxyz); byte[] addrhash160 = pkxyz.Hash160; BitcoinAddress = new AddressBase(addrhash160, networkByte).AddressBase58; // Does the hash160 match? for (int i = 0; i < 20; i++) { if (addrhash160[i] != invbytes[8 + 1 + 1 + 32 + i]) { throw new ArgumentException(notvalid3); } } this.PaymentInvitationCode = paymentInvitationCode; byte expectedabflag = (byte)(escrowInvitationCode.StartsWith("einva") ? 2 : 0); if ((invbytes[8 + 1 + 1 + 32 + 20] & 0x2) != expectedabflag) { SamePartyWarningApplies = true; } }
/// <summary> /// Constructor that takes a single Escrow Invitation Code and produces a Payment Invitation Code. /// </summary> public EscrowCodeSet(string escrowInvitationCode, bool doCompressed = false, byte networkByte = 0) { byte[] pubpart, privpart; int identifier30; string failreason = parseEscrowCode(escrowInvitationCode, out pubpart, out privpart, out identifier30); if (failreason != null) { throw new ArgumentException(failreason); } // Look for mismatched parity. // (we expect LSB of einva's private part to be 0 and LSB of einvb's private part to be 1) // (this is to guarantee that einva's and einvb's private parts aren't equal) if ((escrowInvitationCode.StartsWith("einva") && (privpart[31] & 0x01) == 1) || (escrowInvitationCode.StartsWith("einvb") && (privpart[31] & 0x01) == 0)) { throw new ArgumentException("This escrow invitation has mismatched parity. Ask your escrow agent to " + "generate a new pair using the latest version of the software."); } // Look for 48 0's or 48 1's if (privpart[0] == privpart[1] && privpart[1] == privpart[2] && privpart[2] == privpart[3] && privpart[3] == privpart[4] && privpart[4] == privpart[5] && privpart[5] == privpart[6] && privpart[6] == privpart[7] && privpart[7] == privpart[8]) { if (privpart[0] == 0x00 || privpart[0] == 0xFF) { throw new ArgumentException("This escrow invitation is invalid and cannot be used (bad private key)."); } } // produce a new factor byte[] z = new byte[32]; SecureRandom sr = new SecureRandom(); sr.NextBytes(z); // calculate Gxy then Gxyz PublicKey pk = new PublicKey(pubpart); ECPoint Gxyz = pk.GetECPoint().Multiply(new BigInteger(1, privpart)).Multiply(new BigInteger(1, z)); // Uncompress it Gxyz = PublicKey.GetUncompressed(Gxyz); // We can get the Bitcoin address now, so do so PublicKey pkxyz = new PublicKey(Gxyz); byte[] hash160 = pkxyz.Hash160; BitcoinAddress = new AddressBase(hash160, networkByte).AddressBase58; // make the payment invitation record byte[] invp = new byte[74]; long headP = headbaseP + (long)identifier30; for (int i = 7; i >= 0; i--) { invp[i] = (byte)(headP & 0xff); headP >>= 8; } invp[8] = networkByte; Array.Copy(z, 0, invp, 8 + 1 + 1, 32); // set flag to indicate if einvb was used to generate this, and make it available in the object if (escrowInvitationCode.StartsWith("einvb")) { invp[8 + 1 + 1 + 32 + 20] = 0x2; } // copy hash160 Array.Copy(hash160, 0, invp, 8 + 1 + 1 + 32, 20); PaymentInvitationCode = Util.ByteArrayToBase58Check(invp); setAddressConfirmationCode(identifier30, networkByte, invp[8 + 1 + 1 + 32 + 20], z, hash160); }
/// <summary> /// Constructor that calculates the address given one escrow invitation code and one matching payment invitation code. /// They can be provided in any order. /// </summary> public EscrowCodeSet(string code1, string code2) { if (code1 == null || code2 == null || code1 == "" || code2 == "") { throw new ArgumentException("Two codes are required to use this function."); } string escrowInvitationCode=null, paymentInvitationCode=null; if (code1.StartsWith("einva") || code1.StartsWith("einvb")) escrowInvitationCode = code1; if (code2.StartsWith("einva") || code2.StartsWith("einvb")) escrowInvitationCode = code2; if (code1.StartsWith("einvp")) paymentInvitationCode = code1; if (code2.StartsWith("einvp")) paymentInvitationCode = code2; if (escrowInvitationCode == null || paymentInvitationCode == null) { throw new ArgumentException("In order to use this function, one code MUST be an Escrow Invitation (starting " + "with \"einva\" or \"einvb\") and the other code MUST be a Payment Invitation (starting with \"einvp\")."); } byte[] pubpart, privpart; int identifier30; string failreason = parseEscrowCode(escrowInvitationCode, out pubpart, out privpart, out identifier30); if (failreason != null) throw new ArgumentException(failreason); // Look for first 40 bits being all 0's or all 1's string notvalid = "Not a valid Payment Invitation Code"; string notvalid2 = "Code is not a valid Payment Invitation Code or may have a typo or other error."; string notvalid3 = "The Payment Invitation does not belong to the provided Escrow Invitation."; long head; byte[] invbytes; string failReason = parseEitherCode(paymentInvitationCode, notvalid, notvalid2, out invbytes, out head); if (head < headbaseP) throw new ArgumentException(notvalid); long identifier30L = head - headbaseP; if (identifier30L < 0 || identifier30L > 0x3FFFFFFFL) throw new ArgumentException(notvalid); if ((long)identifier30 != identifier30L) { throw new ArgumentException(notvalid3); } byte[] privpartz = new byte[32]; Array.Copy(invbytes, 8 + 1 + 1, privpartz, 0, 32); byte networkByte = invbytes[8]; bool compressedFlag = (invbytes[8+1+1+32+20] & 0x1) == 1; // get bitcoin address PublicKey pk = new PublicKey(pubpart); ECPoint Gxyz = pk.GetECPoint().Multiply(new BigInteger(1, privpart)).Multiply(new BigInteger(1, privpartz)); // uncompress if compress is not indicated if (compressedFlag == false) Gxyz = PublicKey.GetUncompressed(Gxyz); // We can get the Bitcoin address now, so do so PublicKey pkxyz = new PublicKey(Gxyz); byte[] addrhash160 = pkxyz.Hash160; BitcoinAddress = new AddressBase(addrhash160, networkByte).AddressBase58; // Does the hash160 match? for (int i = 0; i < 20; i++) { if (addrhash160[i] != invbytes[8+1+1+32+i]) { throw new ArgumentException(notvalid3); } } this.PaymentInvitationCode = paymentInvitationCode; byte expectedabflag = (byte)(escrowInvitationCode.StartsWith("einva") ? 2 : 0); if ((invbytes[8+1+1+32+20] & 0x2) != expectedabflag) { SamePartyWarningApplies = true; } }
/// <summary> /// Constructor that takes a single Escrow Invitation Code and produces a Payment Invitation Code. /// </summary> public EscrowCodeSet(string escrowInvitationCode, bool doCompressed=false, byte networkByte = 0) { byte[] pubpart, privpart; int identifier30; string failreason = parseEscrowCode(escrowInvitationCode, out pubpart, out privpart, out identifier30); if (failreason != null) throw new ArgumentException(failreason); // Look for mismatched parity. // (we expect LSB of einva's private part to be 0 and LSB of einvb's private part to be 1) // (this is to guarantee that einva's and einvb's private parts aren't equal) if ((escrowInvitationCode.StartsWith("einva") && (privpart[31] & 0x01) == 1) || (escrowInvitationCode.StartsWith("einvb") && (privpart[31] & 0x01) == 0)) { throw new ArgumentException("This escrow invitation has mismatched parity. Ask your escrow agent to " + "generate a new pair using the latest version of the software."); } // Look for 48 0's or 48 1's if (privpart[0] == privpart[1] && privpart[1] == privpart[2] && privpart[2] == privpart[3] && privpart[3] == privpart[4] && privpart[4] == privpart[5] && privpart[5] == privpart[6] && privpart[6] == privpart[7] && privpart[7] == privpart[8]) { if (privpart[0] == 0x00 || privpart[0] == 0xFF) { throw new ArgumentException("This escrow invitation is invalid and cannot be used (bad private key)."); } } // produce a new factor byte[] z = new byte[32]; SecureRandom sr = new SecureRandom(); sr.NextBytes(z); // calculate Gxy then Gxyz PublicKey pk = new PublicKey(pubpart); ECPoint Gxyz = pk.GetECPoint().Multiply(new BigInteger(1, privpart)).Multiply(new BigInteger(1, z)); // Uncompress it Gxyz = PublicKey.GetUncompressed(Gxyz); // We can get the Bitcoin address now, so do so PublicKey pkxyz = new PublicKey(Gxyz); byte[] hash160 = pkxyz.Hash160; BitcoinAddress = new AddressBase(hash160, networkByte).AddressBase58; // make the payment invitation record byte[] invp = new byte[74]; long headP = headbaseP + (long)identifier30; for (int i = 7; i >= 0; i--) { invp[i] = (byte)(headP & 0xff); headP >>= 8; } invp[8] = networkByte; Array.Copy(z, 0, invp, 8 + 1 + 1, 32); // set flag to indicate if einvb was used to generate this, and make it available in the object if (escrowInvitationCode.StartsWith("einvb")) { invp[8 + 1 + 1 + 32 + 20] = 0x2; } // copy hash160 Array.Copy(hash160, 0, invp, 8 + 1 + 1 + 32, 20); PaymentInvitationCode = Util.ByteArrayToBase58Check(invp); setAddressConfirmationCode(identifier30, networkByte, invp[8 + 1 + 1 + 32 + 20], z, hash160); }
/// <summary> /// Encryption constructor to create a new random key from an intermediate /// </summary> public Bip38KeyPair(Bip38Intermediate intermediate, bool retainPrivateKeyWhenPossible=false) { // generate seedb byte[] seedb = new byte[24]; SecureRandom sr = new SecureRandom(); sr.NextBytes(seedb); // get factorb as sha256(sha256(seedb)) Sha256Digest sha256 = new Sha256Digest(); sha256.BlockUpdate(seedb, 0, 24); factorb = new byte[32]; sha256.DoFinal(factorb, 0); sha256.BlockUpdate(factorb, 0, 32); sha256.DoFinal(factorb, 0); // get ECPoint from passpoint PublicKey pk = new PublicKey(intermediate.passpoint); ECPoint generatedpoint = pk.GetECPoint().Multiply(new BigInteger(1, factorb)); byte[] generatedpointbytes = generatedpoint.GetEncoded(); PublicKey generatedaddress = new PublicKey(generatedpointbytes); // get addresshash UTF8Encoding utf8 = new UTF8Encoding(false); byte[] generatedaddressbytes = utf8.GetBytes(generatedaddress.AddressBase58); sha256.BlockUpdate(generatedaddressbytes, 0, generatedaddressbytes.Length); byte[] addresshashfull = new byte[32]; sha256.DoFinal(addresshashfull, 0); sha256.BlockUpdate(addresshashfull, 0, 32); sha256.DoFinal(addresshashfull, 0); byte[] addresshashplusownerentropy = new byte[12]; Array.Copy(addresshashfull, 0, addresshashplusownerentropy, 0, 4); Array.Copy(intermediate.ownerentropy, 0, addresshashplusownerentropy, 4, 8); // derive encryption key material derived = new byte[64]; SCrypt.ComputeKey(intermediate.passpoint, addresshashplusownerentropy, 1024, 1, 1, 1, derived); byte[] derivedhalf2 = new byte[32]; Array.Copy(derived, 32, derivedhalf2, 0, 32); byte[] unencryptedpart1 = new byte[16]; for (int i = 0; i < 16; i++) { unencryptedpart1[i] = (byte)(seedb[i] ^ derived[i]); } byte[] encryptedpart1 = new byte[16]; // encrypt it var aes = Aes.Create(); aes.KeySize = 256; aes.Mode = CipherMode.ECB; aes.Key = derivedhalf2; ICryptoTransform encryptor = aes.CreateEncryptor(); encryptor.TransformBlock(unencryptedpart1, 0, 16, encryptedpart1, 0); encryptor.TransformBlock(unencryptedpart1, 0, 16, encryptedpart1, 0); byte[] unencryptedpart2 = new byte[16]; for (int i = 0; i < 8; i++) { unencryptedpart2[i] = (byte)(encryptedpart1[i + 8] ^ derived[i + 16]); } for (int i = 0; i < 8; i++) { unencryptedpart2[i + 8] = (byte)(seedb[i + 16] ^ derived[i + 24]); } byte[] encryptedpart2 = new byte[16]; encryptor.TransformBlock(unencryptedpart2, 0, 16, encryptedpart2, 0); encryptor.TransformBlock(unencryptedpart2, 0, 16, encryptedpart2, 0); byte[] result = new byte[39]; result[0] = 0x01; result[1] = 0x43; result[2] = generatedaddress.IsCompressedPoint ? (byte)0x20 : (byte)0x00; if (intermediate.LotSequencePresent) result[2] |= 0x04; Array.Copy(addresshashfull, 0, result, 3, 4); Array.Copy(intermediate.ownerentropy, 0, result, 7, 8); Array.Copy(encryptedpart1, 0, result, 15, 8); Array.Copy(encryptedpart2, 0, result, 23, 16); _encryptedKey = Util.ByteArrayToBase58Check(result); _pubKey = generatedaddress.PublicKeyBytes; _hash160 = generatedaddress.Hash160; var ps = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName("secp256k1"); if (retainPrivateKeyWhenPossible && intermediate.passfactor != null) { BigInteger privatekey = new BigInteger(1, intermediate.passfactor).Multiply(new BigInteger(1, factorb)).Mod(ps.N); _privKey = new KeyPair(privatekey).PrivateKeyBytes; } // create the confirmation code confirmationCodeInfo = new byte[51]; // constant provides for prefix "cfrm38" confirmationCodeInfo[0] = 0x64; confirmationCodeInfo[1] = 0x3B; confirmationCodeInfo[2] = 0xF6; confirmationCodeInfo[3] = 0xA8; confirmationCodeInfo[4] = 0x9A; // fields for flagbyte, addresshash, and ownerentropy all copy verbatim Array.Copy(result, 2, confirmationCodeInfo, 5, 1 + 4 + 8); }
/// <summary> /// Constructor that takes a single Escrow Invitation Code and produces a Payment Invitation Code. /// </summary> public EscrowCodeSet(string escrowInvitationCode, bool doCompressed=false, byte networkByte = 0) { byte[] pubpart, privpart; int identifier30; string failreason = parseEscrowCode(escrowInvitationCode, out pubpart, out privpart, out identifier30); if (failreason != null) throw new ArgumentException(failreason); // produce a new factor byte[] z = new byte[32]; SecureRandom sr = new SecureRandom(); sr.NextBytes(z); // calculate Gxy then Gxyz PublicKey pk = new PublicKey(pubpart); ECPoint Gxyz = pk.GetECPoint().Multiply(new BigInteger(1, privpart)).Multiply(new BigInteger(1, z)); // Uncompress it Gxyz = PublicKey.GetUncompressed(Gxyz); // We can get the Bitcoin address now, so do so PublicKey pkxyz = new PublicKey(Gxyz); byte[] hash160 = pkxyz.Hash160; BitcoinAddress = new AddressBase(hash160, networkByte).AddressBase58; // make the payment invitation record byte[] invp = new byte[74]; long headP = headbaseP + (long)identifier30; for (int i = 7; i >= 0; i--) { invp[i] = (byte)(headP & 0xff); headP >>= 8; } invp[8] = networkByte; Array.Copy(z, 0, invp, 8 + 1 + 1, 32); // set flag to indicate if einvb was used to generate this, and make it available in the object if (escrowInvitationCode.StartsWith("einvb")) { invp[8 + 1 + 1 + 32 + 20] = 0x2; } // copy hash160 Array.Copy(hash160, 0, invp, 8 + 1 + 1 + 32, 20); PaymentInvitationCode = Util.ByteArrayToBase58Check(invp); setAddressConfirmationCode(identifier30, networkByte, invp[8 + 1 + 1 + 32 + 20], z, hash160); }
/// <summary> /// Encryption constructor to create a new random key from an intermediate /// </summary> public Bip38KeyPair(Bip38Intermediate intermediate, bool retainPrivateKeyWhenPossible = false) { // generate seedb byte[] seedb = new byte[24]; SecureRandom sr = new SecureRandom(); sr.NextBytes(seedb); // get factorb as sha256(sha256(seedb)) Sha256Digest sha256 = new Sha256Digest(); sha256.BlockUpdate(seedb, 0, 24); factorb = new byte[32]; sha256.DoFinal(factorb, 0); sha256.BlockUpdate(factorb, 0, 32); sha256.DoFinal(factorb, 0); // get ECPoint from passpoint PublicKey pk = new PublicKey(intermediate.passpoint); ECPoint generatedpoint = pk.GetECPoint().Multiply(new BigInteger(1, factorb)); byte[] generatedpointbytes = generatedpoint.GetEncoded(); PublicKey generatedaddress = new PublicKey(generatedpointbytes); // get addresshash UTF8Encoding utf8 = new UTF8Encoding(false); byte[] generatedaddressbytes = utf8.GetBytes(generatedaddress.AddressBase58); sha256.BlockUpdate(generatedaddressbytes, 0, generatedaddressbytes.Length); byte[] addresshashfull = new byte[32]; sha256.DoFinal(addresshashfull, 0); sha256.BlockUpdate(addresshashfull, 0, 32); sha256.DoFinal(addresshashfull, 0); byte[] addresshashplusownerentropy = new byte[12]; Array.Copy(addresshashfull, 0, addresshashplusownerentropy, 0, 4); Array.Copy(intermediate.ownerentropy, 0, addresshashplusownerentropy, 4, 8); // derive encryption key material derived = new byte[64]; CryptSharp.Utility.SCrypt.ComputeKey(intermediate.passpoint, addresshashplusownerentropy, 1024, 1, 1, 1, derived); byte[] derivedhalf2 = new byte[32]; Array.Copy(derived, 32, derivedhalf2, 0, 32); byte[] unencryptedpart1 = new byte[16]; for (int i = 0; i < 16; i++) { unencryptedpart1[i] = (byte)(seedb[i] ^ derived[i]); } byte[] encryptedpart1 = new byte[16]; // encrypt it var aes = Aes.Create(); aes.KeySize = 256; aes.Mode = CipherMode.ECB; aes.Key = derivedhalf2; ICryptoTransform encryptor = aes.CreateEncryptor(); encryptor.TransformBlock(unencryptedpart1, 0, 16, encryptedpart1, 0); encryptor.TransformBlock(unencryptedpart1, 0, 16, encryptedpart1, 0); byte[] unencryptedpart2 = new byte[16]; for (int i = 0; i < 8; i++) { unencryptedpart2[i] = (byte)(encryptedpart1[i + 8] ^ derived[i + 16]); } for (int i = 0; i < 8; i++) { unencryptedpart2[i + 8] = (byte)(seedb[i + 16] ^ derived[i + 24]); } byte[] encryptedpart2 = new byte[16]; encryptor.TransformBlock(unencryptedpart2, 0, 16, encryptedpart2, 0); encryptor.TransformBlock(unencryptedpart2, 0, 16, encryptedpart2, 0); byte[] result = new byte[39]; result[0] = 0x01; result[1] = 0x43; result[2] = generatedaddress.IsCompressedPoint ? (byte)0x20 : (byte)0x00; if (intermediate.LotSequencePresent) { result[2] |= 0x04; } Array.Copy(addresshashfull, 0, result, 3, 4); Array.Copy(intermediate.ownerentropy, 0, result, 7, 8); Array.Copy(encryptedpart1, 0, result, 15, 8); Array.Copy(encryptedpart2, 0, result, 23, 16); _encryptedKey = Util.ByteArrayToBase58Check(result); _pubKey = generatedaddress.PublicKeyBytes; _hash160 = generatedaddress.Hash160; var ps = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName("secp256k1"); if (retainPrivateKeyWhenPossible && intermediate.passfactor != null) { BigInteger privatekey = new BigInteger(1, intermediate.passfactor).Multiply(new BigInteger(1, factorb)).Mod(ps.N); _privKey = new KeyPair(privatekey).PrivateKeyBytes; } // create the confirmation code confirmationCodeInfo = new byte[51]; // constant provides for prefix "cfrm38" confirmationCodeInfo[0] = 0x64; confirmationCodeInfo[1] = 0x3B; confirmationCodeInfo[2] = 0xF6; confirmationCodeInfo[3] = 0xA8; confirmationCodeInfo[4] = 0x9A; // fields for flagbyte, addresshash, and ownerentropy all copy verbatim Array.Copy(result, 2, confirmationCodeInfo, 5, 1 + 4 + 8); }